Skip to content

Commit

Permalink
OpenX: Native support (prebid#3649)
Browse files Browse the repository at this point in the history
  • Loading branch information
sergseven authored Jan 7, 2025
1 parent 23e9569 commit 2329a4d
Show file tree
Hide file tree
Showing 4 changed files with 239 additions and 34 deletions.
49 changes: 39 additions & 10 deletions src/main/java/org/prebid/server/bidder/openx/OpenxBidder.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class OpenxBidder implements Bidder<BidRequest> {

Expand Down Expand Up @@ -72,9 +73,12 @@ public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest bidRequ
.collect(Collectors.groupingBy(OpenxBidder::resolveImpType));

final List<BidderError> processingErrors = new ArrayList<>();
final List<BidRequest> outgoingRequests = makeRequests(bidRequest,
final List<BidRequest> outgoingRequests = makeRequests(
bidRequest,
differentiatedImps.get(OpenxImpType.banner),
differentiatedImps.get(OpenxImpType.video), processingErrors);
differentiatedImps.get(OpenxImpType.video),
differentiatedImps.get(OpenxImpType.xNative),
processingErrors);

final List<BidderError> errors = errors(differentiatedImps.get(OpenxImpType.other), processingErrors);

Expand All @@ -101,13 +105,21 @@ public Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequ
return Result.withError(BidderError.generic("Deprecated adapter method invoked"));
}

private List<BidRequest> makeRequests(BidRequest bidRequest, List<Imp> bannerImps, List<Imp> videoImps,
List<BidderError> errors) {
private List<BidRequest> makeRequests(
BidRequest bidRequest,
List<Imp> bannerImps,
List<Imp> videoImps,
List<Imp> nativeImps,
List<BidderError> errors) {
final List<BidRequest> bidRequests = new ArrayList<>();
// single request for all banner imps
final BidRequest bannerRequest = createSingleRequest(bannerImps, bidRequest, errors);
if (bannerRequest != null) {
bidRequests.add(bannerRequest);
// single request for all banner and native imps
final List<Imp> bannerAndNativeImps = Stream.of(bannerImps, nativeImps)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.toList();
final BidRequest bannerAndNativeImpsRequest = createSingleRequest(bannerAndNativeImps, bidRequest, errors);
if (bannerAndNativeImpsRequest != null) {
bidRequests.add(bannerAndNativeImpsRequest);
}

if (CollectionUtils.isNotEmpty(videoImps)) {
Expand All @@ -128,16 +140,33 @@ private static OpenxImpType resolveImpType(Imp imp) {
if (imp.getVideo() != null) {
return OpenxImpType.video;
}
if (imp.getXNative() != null) {
return OpenxImpType.xNative;
}
return OpenxImpType.other;
}

private static BidType resolveBidType(Imp imp) {
if (imp.getBanner() != null) {
return BidType.banner;
}
if (imp.getVideo() != null) {
return BidType.video;
}
if (imp.getXNative() != null) {
return BidType.xNative;
}
return BidType.banner;
}

private List<BidderError> errors(List<Imp> notSupportedImps, List<BidderError> processingErrors) {
final List<BidderError> errors = new ArrayList<>();
// add errors for imps with unsupported media types
if (CollectionUtils.isNotEmpty(notSupportedImps)) {
errors.addAll(
notSupportedImps.stream()
.map(imp -> "OpenX only supports banner and video imps. Ignoring imp id=" + imp.getId())
.map(imp ->
"OpenX only supports banner, video and native imps. Ignoring imp id=" + imp.getId())
.map(BidderError::badInput)
.toList());
}
Expand Down Expand Up @@ -276,7 +305,7 @@ private static ExtBidPrebidVideo getVideoInfo(Bid bid) {

private static Map<String, BidType> impIdToBidType(BidRequest bidRequest) {
return bidRequest.getImp().stream()
.collect(Collectors.toMap(Imp::getId, imp -> imp.getBanner() != null ? BidType.banner : BidType.video));
.collect(Collectors.toMap(Imp::getId, OpenxBidder::resolveBidType));
}

private static BidType getBidType(Bid bid, Map<String, BidType> impIdToBidType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
public enum OpenxImpType {

// supported
banner, video,
banner, video, xNative,
// not supported
other
}
2 changes: 2 additions & 0 deletions src/main/resources/bidder-config/openx.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ adapters:
app-media-types:
- banner
- video
- native
site-media-types:
- banner
- video
- native
supported-vendors:
vendor-id: 69
usersync:
Expand Down
220 changes: 197 additions & 23 deletions src/test/java/org/prebid/server/bidder/openx/OpenxBidderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,30 +91,10 @@ public void makeHttpRequestsShouldReturnResultWithErrorWhenAudioImpsPresent() {
assertThat(result.getValue()).isEmpty();
assertThat(result.getErrors()).hasSize(2)
.containsExactly(
BidderError.badInput("OpenX only supports banner and video imps. Ignoring imp id=impId1"),
BidderError.badInput(
"OpenX only supports banner and video imps. Ignoring imp id=impId2"));
}

@Test
public void makeHttpRequestsShouldReturnResultWithErrorWhenNativeImpsPresent() {
// given
final BidRequest bidRequest = BidRequest.builder()
.imp(asList(
Imp.builder().id("impId1").xNative(Native.builder().build()).build(),
Imp.builder().id("impId2").xNative(Native.builder().build()).build()))
.build();

// when
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);

// then
assertThat(result.getValue()).isEmpty();
assertThat(result.getErrors()).hasSize(2)
.containsExactly(
BidderError.badInput("OpenX only supports banner and video imps. Ignoring imp id=impId1"),
"OpenX only supports banner, video and native imps. Ignoring imp id=impId1"),
BidderError.badInput(
"OpenX only supports banner and video imps. Ignoring imp id=impId2"));
"OpenX only supports banner, video and native imps. Ignoring imp id=impId2"));
}

@Test
Expand Down Expand Up @@ -254,7 +234,7 @@ public void makeHttpRequestsShouldReturnResultWithExpectedFieldsSet() {
// then
assertThat(result.getErrors()).hasSize(1)
.containsExactly(BidderError.badInput(
"OpenX only supports banner and video imps. Ignoring imp id=impId1"));
"OpenX only supports banner, video and native imps. Ignoring imp id=impId1"));

assertThat(result.getValue()).hasSize(3)
.extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class))
Expand Down Expand Up @@ -344,6 +324,158 @@ public void makeHttpRequestsShouldReturnResultWithExpectedFieldsSet() {
.build());
}

@Test
public void makeHttpRequestsShouldReturnResultWithSingleBidRequestForMultipleBannerAndNativeImps() {
// given
final BidRequest bidRequest = BidRequest.builder()
.id("bidRequestId")
.imp(asList(
Imp.builder()
.id("impId4")
.banner(Banner.builder().build())
.ext(mapper.valueToTree(
ExtPrebid.of(null,
ExtImpOpenx.builder()
.customParams(givenCustomParams("foo4", "bar4"))
.delDomain("se-demo-d.openx.net")
.unit("4").build()))).build(),
Imp.builder()
.id("impId5")
.xNative(Native.builder().request("{\"testreq\":1}").build())
.ext(mapper.valueToTree(
ExtPrebid.of(null,
ExtImpOpenx.builder()
.customParams(givenCustomParams("foo5", "bar5"))
.delDomain("se-demo-d.openx.net")
.unit("5").build()))).build(),
Imp.builder()
.id("impId6")
.xNative(Native.builder().build())
.ext(mapper.valueToTree(
ExtPrebid.of(null,
ExtImpOpenx.builder()
.customParams(givenCustomParams("foo6", "bar6"))
.delDomain("se-demo-d.openx.net")
.unit("6").build()))).build()))
.user(User.builder().ext(ExtUser.builder().consent("consent").build()).build())
.regs(Regs.builder().coppa(0).ext(ExtRegs.of(1, null, null, null)).build())
.build();

// when
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);

// then
assertThat(result.getErrors()).isEmpty();

assertThat(result.getValue()).hasSize(1)
.extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class))
.containsExactly(
// check if all native and banner imps are part of single bidRequest
BidRequest.builder()
.id("bidRequestId")
.imp(asList(
Imp.builder()
.id("impId4")
.tagid("4")
.banner(Banner.builder().build())
.ext(mapper.valueToTree(
ExtImpOpenx.builder()
.customParams(
givenCustomParams("foo4", "bar4"))
.build()))
.build(),
Imp.builder()
.id("impId5")
.tagid("5")
.xNative(Native.builder().request("{\"testreq\":1}").build())
.ext(mapper.valueToTree(
ExtImpOpenx.builder()
.customParams(
givenCustomParams("foo5", "bar5"))
.build()))
.build(),
Imp.builder()
.id("impId6")
.tagid("6")
.xNative(Native.builder().build())
.ext(mapper.valueToTree(
ExtImpOpenx.builder()
.customParams(
givenCustomParams("foo6", "bar6"))
.build()))
.build()))
.ext(jacksonMapper.fillExtension(
ExtRequest.empty(),
OpenxRequestExt.of("se-demo-d.openx.net", null, "hb_pbs_1.0.0")))
.user(User.builder()
.ext(ExtUser.builder().consent("consent").build())
.build())
.regs(Regs.builder().coppa(0).ext(ExtRegs.of(1, null, null, null)).build())
.build());
}

@Test
public void makeHttpRequestsShouldReturnResultWithSingleBidRequestForMultiFormatImps() {
// given
final BidRequest bidRequest = BidRequest.builder()
.id("bidRequestId")
.imp(asList(
Imp.builder()
.id("impId1")
.banner(Banner.builder().w(320).h(200).build())
.video(Video.builder().maxduration(10).build())
.ext(mapper.valueToTree(
ExtPrebid.of(null, ExtImpOpenx.builder().unit("1").build())))
.build(),
Imp.builder()
.id("impId2")
.banner(Banner.builder().w(300).h(150).build())
.xNative(Native.builder().request("{\"version\":1}").build())
.ext(mapper.valueToTree(
ExtPrebid.of(null, ExtImpOpenx.builder().unit("2").build())))
.build()))
.user(User.builder().ext(ExtUser.builder().consent("consent").build()).build())
.regs(Regs.builder().coppa(0).ext(ExtRegs.of(1, null, null, null)).build())
.build();

// when
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);

// then
assertThat(result.getErrors()).isEmpty();

assertThat(result.getValue()).hasSize(1)
.extracting(httpRequest -> mapper.readValue(httpRequest.getBody(), BidRequest.class))
.containsExactly(
// check if all native and banner imps are part of single bidRequest
BidRequest.builder()
.id("bidRequestId")
.imp(asList(
// verify banner and video media types are preserved in a single imp
Imp.builder()
.id("impId1")
.tagid("1")
.banner(Banner.builder().w(320).h(200).build())
.video(Video.builder().maxduration(10).build())
.ext(mapper.valueToTree(ExtImpOpenx.builder().build())).build(),
// verify banner and native media types are preserved in a single imp
Imp.builder()
.id("impId2")
.tagid("2")
.banner(Banner.builder().w(300).h(150).build())
.xNative(Native.builder().request("{\"version\":1}").build())
.ext(mapper.valueToTree(ExtImpOpenx.builder().build()))
.build()))
.ext(jacksonMapper.fillExtension(
ExtRequest.empty(),
OpenxRequestExt.of(null, null, "hb_pbs_1.0.0")))
.user(User.builder()
.ext(ExtUser.builder().consent("consent").build())
.build())
.regs(Regs.builder().coppa(0).ext(ExtRegs.of(1, null, null, null)).build())
.build());
}

@Test
public void makeHttpRequestsShouldPassThroughImpExt() {
// given
Expand Down Expand Up @@ -523,6 +655,48 @@ public void makeBidsShouldReturnResultWithExpectedFields() throws JsonProcessing
.build());
}

@Test
public void makeBidsShouldReturnResultForNativeBidsWithExpectedFields() throws JsonProcessingException {
// given
final BidderCall<BidRequest> httpCall = givenHttpCall(mapper.writeValueAsString(OpenxBidResponse.builder()
.seatbid(singletonList(SeatBid.builder()
.bid(singletonList(Bid.builder()
.w(200)
.h(150)
.price(BigDecimal.ONE)
.impid("impId1")
.adm("{\"ver\":\"1.2\"}")
.build()))
.build()))
.cur("UAH")
.ext(OpenxBidResponseExt.of(Map.of("impId1", mapper.createObjectNode().put("somevalue", 1))))
.build()));

final BidRequest bidRequest = BidRequest.builder()
.id("bidRequestId")
.imp(singletonList(Imp.builder()
.id("impId1")
.xNative(Native.builder().request("{\"ver\":\"1.2\",\"plcmttype\":3}").build())
.build()))
.build();

// when
final CompositeBidderResponse result = target.makeBidderResponse(httpCall, bidRequest);

// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getBids()).hasSize(1)
.containsOnly(BidderBid.of(
Bid.builder()
.impid("impId1")
.price(BigDecimal.ONE)
.w(200)
.h(150)
.adm("{\"ver\":\"1.2\"}")
.build(),
BidType.xNative, "UAH"));
}

@Test
public void makeBidsShouldReturnVideoInfoWhenAvailable() throws JsonProcessingException {
// given
Expand Down

0 comments on commit 2329a4d

Please sign in to comment.