diff --git a/src/main/java/org/prebid/server/bidder/openx/OpenxBidder.java b/src/main/java/org/prebid/server/bidder/openx/OpenxBidder.java index 09412abe65f..00a8c8dc3fb 100644 --- a/src/main/java/org/prebid/server/bidder/openx/OpenxBidder.java +++ b/src/main/java/org/prebid/server/bidder/openx/OpenxBidder.java @@ -17,6 +17,7 @@ import org.prebid.server.bidder.model.HttpRequest; import org.prebid.server.bidder.model.Result; import org.prebid.server.bidder.openx.model.OpenxImpType; +import org.prebid.server.bidder.openx.proto.OpenxBidExt; import org.prebid.server.bidder.openx.proto.OpenxBidResponse; import org.prebid.server.bidder.openx.proto.OpenxBidResponseExt; import org.prebid.server.bidder.openx.proto.OpenxRequestExt; @@ -29,6 +30,8 @@ import org.prebid.server.proto.openrtb.ext.request.ExtRequest; import org.prebid.server.proto.openrtb.ext.request.openx.ExtImpOpenx; import org.prebid.server.proto.openrtb.ext.response.BidType; +import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebid; +import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebidMeta; import org.prebid.server.proto.openrtb.ext.response.ExtBidPrebidVideo; import org.prebid.server.proto.openrtb.ext.response.ExtIgi; import org.prebid.server.proto.openrtb.ext.response.ExtIgiIgs; @@ -270,13 +273,13 @@ private ObjectNode makeImpExt(ObjectNode impExt, boolean addCustomParams) { return openxImpExt; } - private static List extractBids(BidRequest bidRequest, OpenxBidResponse bidResponse) { + private List extractBids(BidRequest bidRequest, OpenxBidResponse bidResponse) { return bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid()) ? Collections.emptyList() : bidsFromResponse(bidRequest, bidResponse); } - private static List bidsFromResponse(BidRequest bidRequest, OpenxBidResponse bidResponse) { + private List bidsFromResponse(BidRequest bidRequest, OpenxBidResponse bidResponse) { final Map impIdToBidType = impIdToBidType(bidRequest); final String bidCurrency = StringUtils.isNotBlank(bidResponse.getCur()) @@ -292,11 +295,11 @@ private static List bidsFromResponse(BidRequest bidRequest, OpenxBidR .toList(); } - private static BidderBid toBidderBid(Bid bid, Map impIdToBidType, String bidCurrency) { + private BidderBid toBidderBid(Bid bid, Map impIdToBidType, String bidCurrency) { final BidType bidType = getBidType(bid, impIdToBidType); final ExtBidPrebidVideo videoInfo = bidType == BidType.video ? getVideoInfo(bid) : null; return BidderBid.builder() - .bid(bid) + .bid(bid.toBuilder().ext(getBidExt(bid)).build()) .type(bidType) .bidCurrency(bidCurrency) .videoInfo(videoInfo) @@ -334,4 +337,48 @@ private static List extractIgi(OpenxBidResponse bidResponse) { return igs.isEmpty() ? null : Collections.singletonList(ExtIgi.builder().igs(igs).build()); } + + private ObjectNode getBidExt(Bid bid) { + final ObjectNode ext = bid.getExt(); + if (ext == null) { + return null; + } + + final OpenxBidExt openxBidExt = parseOpenxBidExt(ext); + final Integer buyerId = parseStringToInt(openxBidExt.getBuyerId()); + final Integer dspId = parseStringToInt(openxBidExt.getDspId()); + final Integer brandId = parseStringToInt(openxBidExt.getBrandId()); + + if (buyerId == null && dspId == null && brandId == null) { + return ext; + } + + final ExtBidPrebidMeta meta = ExtBidPrebidMeta.builder() + .networkId(dspId) + .advertiserId(buyerId) + .brandId(brandId) + .build(); + + final ExtBidPrebid extBidPrebid = ExtBidPrebid.builder().meta(meta).build(); + + ext.set(PREBID_EXT, mapper.mapper().valueToTree(extBidPrebid)); + + return ext; + } + + private OpenxBidExt parseOpenxBidExt(ObjectNode ext) { + try { + return mapper.mapper().convertValue(ext, OpenxBidExt.class); + } catch (IllegalArgumentException e) { + return OpenxBidExt.builder().build(); + } + } + + private static Integer parseStringToInt(String value) { + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + return null; + } + } } diff --git a/src/main/java/org/prebid/server/bidder/openx/proto/OpenxBidExt.java b/src/main/java/org/prebid/server/bidder/openx/proto/OpenxBidExt.java new file mode 100644 index 00000000000..00305cfb64b --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/openx/proto/OpenxBidExt.java @@ -0,0 +1,15 @@ +package org.prebid.server.bidder.openx.proto; + +import lombok.Builder; +import lombok.Value; + +@Builder +@Value +public class OpenxBidExt { + + String dspId; + + String buyerId; + + String brandId; +} diff --git a/src/test/java/org/prebid/server/bidder/openx/OpenxBidderTest.java b/src/test/java/org/prebid/server/bidder/openx/OpenxBidderTest.java index 461f300c051..fab9bedceb7 100644 --- a/src/test/java/org/prebid/server/bidder/openx/OpenxBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/openx/OpenxBidderTest.java @@ -23,6 +23,7 @@ import org.prebid.server.bidder.model.HttpRequest; import org.prebid.server.bidder.model.HttpResponse; import org.prebid.server.bidder.model.Result; +import org.prebid.server.bidder.openx.proto.OpenxBidExt; import org.prebid.server.bidder.openx.proto.OpenxBidResponse; import org.prebid.server.bidder.openx.proto.OpenxBidResponseExt; import org.prebid.server.bidder.openx.proto.OpenxRequestExt; @@ -983,6 +984,155 @@ public void makeBidsShouldReturnResultContainingEmptyValueAndErrorsWhenSeatBidEm .containsOnly(Collections.emptyList(), Collections.emptyList()); } + @Test + public void makeBidShouldReturnBidWithExtPrebidMetaContainingAllFieldsFromBidExt() throws JsonProcessingException { + // given + final ObjectNode bidExt = mapper.valueToTree(OpenxBidExt.builder() + .dspId("1") + .buyerId("2") + .brandId("3") + .build()); + final BidderCall httpCall = givenHttpCall(mapper.writeValueAsString( + BidResponse.builder() + .seatbid(singletonList(SeatBid.builder() + .bid(List.of( + Bid.builder() + .w(200) + .h(150) + .price(BigDecimal.ONE) + .impid("impId1") + .dealid("dealid") + .adm("
This is an Ad
") + .ext(bidExt) + .build())) + .build())) + .build())); + + final BidRequest bidRequest = BidRequest.builder() + .id("bidRequestId") + .imp(List.of( + Imp.builder() + .id("impId1") + .banner(Banner.builder().build()) + .build())) + .build(); + + // when + final CompositeBidderResponse result = target.makeBidderResponse(httpCall, bidRequest); + + // then + final ObjectNode expectedExtWithBidMeta = mapper.createObjectNode() + .put("dsp_id", "1") + .put("buyer_id", "2") + .put("brand_id", "3") + .set("prebid", mapper.createObjectNode() + .set("meta", mapper.createObjectNode() + .put("advertiserId", 2) + .put("brandId", 3) + .put("networkId", 1))); + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getBids()).hasSize(1) + .extracting(BidderBid::getBid) + .extracting(Bid::getExt) + .containsExactly(expectedExtWithBidMeta); + } + + @Test + public void makeBidShouldReturnBidWithExtPrebidMetaContainingBrandIdFieldOnly() throws JsonProcessingException { + // given + final ObjectNode bidExt = mapper.valueToTree(OpenxBidExt.builder() + .brandId("4") + .build()); + final BidderCall httpCall = givenHttpCall(mapper.writeValueAsString( + BidResponse.builder() + .seatbid(singletonList(SeatBid.builder() + .bid(List.of( + Bid.builder() + .w(200) + .h(150) + .price(BigDecimal.ONE) + .impid("impId1") + .dealid("dealid") + .adm("
This is an Ad
") + .ext(bidExt) + .build())) + .build())) + .build())); + + final BidRequest bidRequest = BidRequest.builder() + .id("bidRequestId") + .imp(List.of( + Imp.builder() + .id("impId1") + .banner(Banner.builder().build()) + .build())) + .build(); + + // when + final CompositeBidderResponse result = target.makeBidderResponse(httpCall, bidRequest); + + // then + final ObjectNode expectedExtWithBidMeta = mapper.createObjectNode() + .put("brand_id", "4") + .set("prebid", mapper.createObjectNode() + .set("meta", mapper.createObjectNode() + .put("brandId", 4))); + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getBids()).hasSize(1) + .extracting(BidderBid::getBid) + .extracting(Bid::getExt) + .containsExactly(expectedExtWithBidMeta); + } + + @Test + public void makeBidShouldReturnBidWithExtPrebidMetaNotContainingFieldsWithInvalidValues() + throws JsonProcessingException { + // given + final ObjectNode bidExt = mapper.valueToTree(OpenxBidExt.builder() + .dspId("abc") + .buyerId("xyz") + .brandId("cba") + .build()); + final BidderCall httpCall = givenHttpCall(mapper.writeValueAsString( + BidResponse.builder() + .seatbid(singletonList(SeatBid.builder() + .bid(List.of( + Bid.builder() + .w(200) + .h(150) + .price(BigDecimal.ONE) + .impid("impId1") + .dealid("dealid") + .adm("
This is an Ad
") + .ext(bidExt) + .build())) + .build())) + .build())); + + final BidRequest bidRequest = BidRequest.builder() + .id("bidRequestId") + .imp(List.of( + Imp.builder() + .id("impId1") + .banner(Banner.builder().build()) + .build())) + .build(); + + // when + final CompositeBidderResponse result = target.makeBidderResponse(httpCall, bidRequest); + + // then + final ObjectNode expectedExtWithBidMeta = mapper.createObjectNode() + .put("dsp_id", "abc") + .put("buyer_id", "xyz") + .put("brand_id", "cba"); + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getBids()).hasSize(1) + .extracting(BidderBid::getBid) + .extracting(Bid::getExt) + .containsExactly(expectedExtWithBidMeta); + } + private static Map givenCustomParams(String key, Object values) { return singletonMap(key, mapper.valueToTree(values)); }