From 219fc1ac043f7b69360751f75730a9099b1ffc90 Mon Sep 17 00:00:00 2001 From: "chinmay.b" Date: Tue, 16 Apr 2024 15:54:58 +0530 Subject: [PATCH 1/3] Medianet: Fledge support --- .../bidder/medianet/MedianetBidder.java | 53 +++++++- .../response/InterestGroupAuctionBuyer.java | 23 ++++ .../response/InterestGroupAuctionIntent.java | 15 +++ .../response/InterestGroupAuctionSeller.java | 16 +++ .../model/response/MedianetBidResponse.java | 26 ++++ .../response/MedianetBidResponseExt.java | 9 ++ .../bidder/medianet/MedianetBidderTest.java | 123 ++++++++++++++---- 7 files changed, 235 insertions(+), 30 deletions(-) create mode 100644 src/main/java/org/prebid/server/bidder/medianet/model/response/InterestGroupAuctionBuyer.java create mode 100644 src/main/java/org/prebid/server/bidder/medianet/model/response/InterestGroupAuctionIntent.java create mode 100644 src/main/java/org/prebid/server/bidder/medianet/model/response/InterestGroupAuctionSeller.java create mode 100644 src/main/java/org/prebid/server/bidder/medianet/model/response/MedianetBidResponse.java create mode 100644 src/main/java/org/prebid/server/bidder/medianet/model/response/MedianetBidResponseExt.java diff --git a/src/main/java/org/prebid/server/bidder/medianet/MedianetBidder.java b/src/main/java/org/prebid/server/bidder/medianet/MedianetBidder.java index 2e88d1becd7..e67b92976dc 100644 --- a/src/main/java/org/prebid/server/bidder/medianet/MedianetBidder.java +++ b/src/main/java/org/prebid/server/bidder/medianet/MedianetBidder.java @@ -3,19 +3,23 @@ import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Imp; import com.iab.openrtb.response.Bid; -import com.iab.openrtb.response.BidResponse; import com.iab.openrtb.response.SeatBid; import org.apache.commons.collections4.CollectionUtils; import org.prebid.server.bidder.Bidder; +import org.prebid.server.bidder.medianet.model.response.InterestGroupAuctionIntent; +import org.prebid.server.bidder.medianet.model.response.MedianetBidResponse; +import org.prebid.server.bidder.medianet.model.response.MedianetBidResponseExt; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderCall; import org.prebid.server.bidder.model.BidderError; +import org.prebid.server.bidder.model.CompositeBidderResponse; import org.prebid.server.bidder.model.HttpRequest; import org.prebid.server.bidder.model.Result; import org.prebid.server.exception.PreBidException; import org.prebid.server.json.DecodeException; import org.prebid.server.json.JacksonMapper; import org.prebid.server.proto.openrtb.ext.response.BidType; +import org.prebid.server.proto.openrtb.ext.response.FledgeAuctionConfig; import org.prebid.server.util.BidderUtil; import org.prebid.server.util.HttpUtil; @@ -24,6 +28,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Optional; public class MedianetBidder implements Bidder { @@ -40,11 +45,15 @@ public Result>> makeHttpRequests(BidRequest bidRequ return Result.withValue(BidderUtil.defaultRequest(bidRequest, endpointUrl, mapper)); } + /** + * @deprecated for this bidder in favor of @link{makeBidderResponse} which supports additional response data + */ @Override + @Deprecated(forRemoval = true) public final Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { - final BidResponse bidResponse; + final MedianetBidResponse bidResponse; try { - bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class); + bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), MedianetBidResponse.class); } catch (DecodeException e) { return Result.withError(BidderError.badServerResponse(e.getMessage())); } @@ -55,7 +64,27 @@ public final Result> makeBids(BidderCall httpCall, B return Result.of(bids, errors); } - private static List extractBids(BidRequest bidRequest, BidResponse bidResponse, + @Override + public final CompositeBidderResponse makeBidderResponse(BidderCall httpCall, BidRequest bidRequest) { + final MedianetBidResponse bidResponse; + try { + bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), MedianetBidResponse.class); + } catch (DecodeException e) { + return CompositeBidderResponse.withError(BidderError.badServerResponse(e.getMessage())); + } + + final List errors = new ArrayList<>(); + final List bids = extractBids(httpCall.getRequest().getPayload(), bidResponse, errors); + final List fledgeAuctionConfigs = extractFledge(bidResponse); + + return CompositeBidderResponse.builder() + .bids(bids) + .fledgeAuctionConfigs(fledgeAuctionConfigs) + .errors(errors) + .build(); + } + + private static List extractBids(BidRequest bidRequest, MedianetBidResponse bidResponse, List errors) { if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) { return Collections.emptyList(); @@ -84,9 +113,8 @@ private static BidType resolveBidType(Bid bid, List imps) { case 2 -> BidType.video; case 3 -> BidType.audio; case 4 -> BidType.xNative; - default -> - throw new PreBidException("Unable to fetch mediaType: %s" - .formatted(bid.getImpid())); + default -> throw new PreBidException("Unable to fetch mediaType: %s" + .formatted(bid.getImpid())); }; } @@ -119,4 +147,15 @@ private static BidType resolveBidTypeFromImpId(String impId, List imps) { return BidType.banner; } + + private static List extractFledge(MedianetBidResponse bidResponse) { + return Optional.ofNullable(bidResponse) + .map(MedianetBidResponse::getExt) + .map(MedianetBidResponseExt::getIgi) + .map(InterestGroupAuctionIntent::getIgs) + .orElse(Collections.emptyList()) + .stream() + .map(e -> FledgeAuctionConfig.builder().impId(e.getImpId()).config(e.getConfig()).build()) + .toList(); + } } diff --git a/src/main/java/org/prebid/server/bidder/medianet/model/response/InterestGroupAuctionBuyer.java b/src/main/java/org/prebid/server/bidder/medianet/model/response/InterestGroupAuctionBuyer.java new file mode 100644 index 00000000000..04340fd8321 --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/medianet/model/response/InterestGroupAuctionBuyer.java @@ -0,0 +1,23 @@ +package org.prebid.server.bidder.medianet.model.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.Value; + +@Value +public class InterestGroupAuctionBuyer { + + String origin; + + @JsonProperty("maxbid") + Double maxBid; + + @JsonProperty("cur") + String currency; + + @JsonProperty("pbs") + String buyerSignals; + + @JsonProperty("ps") + ObjectNode prioritySignals; +} diff --git a/src/main/java/org/prebid/server/bidder/medianet/model/response/InterestGroupAuctionIntent.java b/src/main/java/org/prebid/server/bidder/medianet/model/response/InterestGroupAuctionIntent.java new file mode 100644 index 00000000000..e060c1fb5dd --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/medianet/model/response/InterestGroupAuctionIntent.java @@ -0,0 +1,15 @@ +package org.prebid.server.bidder.medianet.model.response; + +import lombok.Builder; +import lombok.Value; + +import java.util.List; + +@Builder +@Value +public class InterestGroupAuctionIntent { + + List igb; + + List igs; +} diff --git a/src/main/java/org/prebid/server/bidder/medianet/model/response/InterestGroupAuctionSeller.java b/src/main/java/org/prebid/server/bidder/medianet/model/response/InterestGroupAuctionSeller.java new file mode 100644 index 00000000000..15ae3ade13b --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/medianet/model/response/InterestGroupAuctionSeller.java @@ -0,0 +1,16 @@ +package org.prebid.server.bidder.medianet.model.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.Builder; +import lombok.Value; + +@Builder +@Value +public class InterestGroupAuctionSeller { + + @JsonProperty(value = "impid") + String impId; + + ObjectNode config; +} diff --git a/src/main/java/org/prebid/server/bidder/medianet/model/response/MedianetBidResponse.java b/src/main/java/org/prebid/server/bidder/medianet/model/response/MedianetBidResponse.java new file mode 100644 index 00000000000..4677294e6fc --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/medianet/model/response/MedianetBidResponse.java @@ -0,0 +1,26 @@ +package org.prebid.server.bidder.medianet.model.response; + +import com.iab.openrtb.response.SeatBid; +import lombok.Builder; +import lombok.Value; + +import java.util.List; + +@Value +@Builder +public class MedianetBidResponse { + + String id; + + List seatbid; + + String bidid; + + String cur; + + String customdata; + + Integer nbr; + + MedianetBidResponseExt ext; +} diff --git a/src/main/java/org/prebid/server/bidder/medianet/model/response/MedianetBidResponseExt.java b/src/main/java/org/prebid/server/bidder/medianet/model/response/MedianetBidResponseExt.java new file mode 100644 index 00000000000..80497830407 --- /dev/null +++ b/src/main/java/org/prebid/server/bidder/medianet/model/response/MedianetBidResponseExt.java @@ -0,0 +1,9 @@ +package org.prebid.server.bidder.medianet.model.response; + +import lombok.Value; + +@Value(staticConstructor = "of") +public class MedianetBidResponseExt { + + InterestGroupAuctionIntent igi; +} diff --git a/src/test/java/org/prebid/server/bidder/medianet/MedianetBidderTest.java b/src/test/java/org/prebid/server/bidder/medianet/MedianetBidderTest.java index fb541c35d5d..7a0dd76d178 100644 --- a/src/test/java/org/prebid/server/bidder/medianet/MedianetBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/medianet/MedianetBidderTest.java @@ -9,18 +9,25 @@ import com.iab.openrtb.response.SeatBid; import org.junit.Test; import org.prebid.server.VertxTest; +import org.prebid.server.bidder.medianet.model.response.InterestGroupAuctionIntent; +import org.prebid.server.bidder.medianet.model.response.InterestGroupAuctionSeller; +import org.prebid.server.bidder.medianet.model.response.MedianetBidResponse; +import org.prebid.server.bidder.medianet.model.response.MedianetBidResponseExt; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderCall; import org.prebid.server.bidder.model.BidderError; +import org.prebid.server.bidder.model.CompositeBidderResponse; 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.proto.openrtb.ext.ExtPrebid; +import org.prebid.server.proto.openrtb.ext.response.FledgeAuctionConfig; import java.util.ArrayList; import java.util.List; import java.util.function.Function; +import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @@ -63,7 +70,7 @@ public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() { final BidderCall httpCall = sampleHttpCall(givenBidRequest(), "invalid response"); // when - final Result> result = target.makeBids(httpCall, null); + final CompositeBidderResponse result = target.makeBidderResponse(httpCall, null); // then assertThat(result.getErrors()).hasSize(1) @@ -78,11 +85,11 @@ public void makeBidsShouldReturnEmptyListIfBidResponseIsNull() throws JsonProces httpCall = sampleHttpCall(givenBidRequest(), mapper.writeValueAsString(null)); // when - final Result> result = target.makeBids(httpCall, null); + final CompositeBidderResponse result = target.makeBidderResponse(httpCall, null); // then + assertThat(result.getBids()).isEmpty(); assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).isEmpty(); } @Test @@ -92,11 +99,11 @@ public void makeBidsShouldReturnEmptyListIfBidResponseSeatBidIsNull() throws Jso httpCall = sampleHttpCall(null, mapper.writeValueAsString(BidResponse.builder().build())); // when - final Result> result = target.makeBids(httpCall, null); + final CompositeBidderResponse result = target.makeBidderResponse(httpCall, null); // then + assertThat(result.getBids()).isEmpty(); assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).isEmpty(); } @Test @@ -107,12 +114,12 @@ public void makeBidsShouldReturnBannerBidIfBannerIsPresent() throws JsonProcessi mapper.writeValueAsString(sampleBidResponse(bidBuilder -> bidBuilder.impid("123")))); // when - final Result> result = target.makeBids(httpCall, null); + final CompositeBidderResponse result = target.makeBidderResponse(httpCall, null); // then - assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()) + assertThat(result.getBids()) .containsExactly(BidderBid.of(Bid.builder().impid("123").build(), banner, "USD")); + assertThat(result.getErrors()).isEmpty(); } @Test @@ -133,16 +140,16 @@ public void makeBidsShouldReturnCorrespondingMtypesAndAdTypes() throws JsonProce mapper.writeValueAsString(sampleMultiFormatBidResponse(bids))); // when - final Result> result = target.makeBids(httpCall, null); + final CompositeBidderResponse result = target.makeBidderResponse(httpCall, null); // then + assertThat(result.getBids()).hasSize(4); assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(4); final BidderBid bannerBid = BidderBid.of(bid1, banner, "USD"); final BidderBid videoBid = BidderBid.of(bid2, video, "USD"); final BidderBid audioBid = BidderBid.of(bid3, audio, "USD"); final BidderBid xNativeBid = BidderBid.of(bid4, xNative, "USD"); - assertThat(result.getValue()).containsExactlyInAnyOrder(bannerBid, videoBid, audioBid, xNativeBid); + assertThat(result.getBids()).containsExactlyInAnyOrder(bannerBid, videoBid, audioBid, xNativeBid); } @Test @@ -153,13 +160,13 @@ public void makeBidsShouldReturnAdTypeAccordingToImpressionIfMtypeIsAbsent() thr mapper.writeValueAsString(sampleBidResponse(bidBuilder -> bidBuilder.impid("imp_id")))); // when - final Result> result = target.makeBids(httpCall, null); + final CompositeBidderResponse result = target.makeBidderResponse(httpCall, null); // then + assertThat(result.getBids()).hasSize(1); assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1); final BidderBid videoBid = BidderBid.of(Bid.builder().impid("imp_id").build(), video, "USD"); - assertThat(result.getValue()).containsExactly(videoBid); + assertThat(result.getBids()).containsExactly(videoBid); } @Test @@ -171,13 +178,13 @@ public void makeBidsShouldReturnBannerAdTypeIfMtypeIsAbsentAndIfNoImpressionIdMa mapper.writeValueAsString(sampleBidResponse(bidBuilder -> bidBuilder.impid("imp_id2")))); // when - final Result> result = target.makeBids(httpCall, null); + final CompositeBidderResponse result = target.makeBidderResponse(httpCall, null); // then + assertThat(result.getBids()).hasSize(1); assertThat(result.getErrors()).isEmpty(); - assertThat(result.getValue()).hasSize(1); final BidderBid bannerBid = BidderBid.of(Bid.builder().impid("imp_id2").build(), banner, "USD"); - assertThat(result.getValue()).containsExactly(bannerBid); + assertThat(result.getBids()).containsExactly(bannerBid); } @Test @@ -188,18 +195,58 @@ public void makeBidsShouldReturnErrorIfMtypeIsWrong() throws JsonProcessingExcep mapper.writeValueAsString(sampleBidResponse(bidBuilder -> bidBuilder.impid("imp_id").mtype(5)))); // when - final Result> result = target.makeBids(httpCall, null); + final CompositeBidderResponse result = target.makeBidderResponse(httpCall, null); // then + assertThat(result.getBids()).isEmpty(); assertThat(result.getErrors()).hasSize(1); - assertThat(result.getValue()).isEmpty(); final BidderError error = BidderError.badServerResponse("Unable to fetch mediaType: imp_id"); assertThat(result.getErrors()).containsExactly(error); } - private static BidResponse sampleBidResponse(Function httpCall = sampleHttpCall( + givenBidRequest(), + mapper.writeValueAsString(sampleBidResponseWithFledgeConfig(bidBuilder -> bidBuilder.impid("imp_id")))); + + // when + final CompositeBidderResponse result = target.makeBidderResponse(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getBids()).hasSize(1); + assertThat(result.getFledgeAuctionConfigs()) + .containsOnly(FledgeAuctionConfig.builder() + .impId("imp_id") + .config(mapper.createObjectNode().put("someKey", "someValue")) + .build()); + } + + @Test + public void makeBidsShouldReturnFledgeConfigIfBidIsAbsent() throws JsonProcessingException { + // given + final BidderCall httpCall = sampleHttpCall( + givenBidRequest(), + mapper.writeValueAsString(sampleBidResponseWithoutBidAndWithFledgeConfig("imp_id"))); + + // when + final CompositeBidderResponse result = target.makeBidderResponse(httpCall, null); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getBids()).isEmpty(); + assertThat(result.getFledgeAuctionConfigs()) + .containsOnly(FledgeAuctionConfig.builder() + .impId("imp_id") + .config(mapper.createObjectNode().put("someKey", "someValue")) + .build()); + } + + private static MedianetBidResponse sampleBidResponse(Function bidCustomizer) { - return BidResponse.builder() + return MedianetBidResponse.builder() .cur("USD") .seatbid(singletonList(SeatBid.builder() .bid(singletonList(bidCustomizer.apply(Bid.builder()).build())) @@ -207,8 +254,38 @@ private static BidResponse sampleBidResponse(Function bids) { - return BidResponse.builder() + private static MedianetBidResponse sampleBidResponseWithFledgeConfig(Function bidCustomizer) { + final Bid bid = bidCustomizer.apply(Bid.builder()).build(); + return MedianetBidResponse.builder() + .cur("USD") + .seatbid(singletonList(SeatBid.builder() + .bid(singletonList(bid)) + .build())) + .ext(MedianetBidResponseExt.of(InterestGroupAuctionIntent.builder() + .igs(List.of(InterestGroupAuctionSeller.builder() + .impId(bid.getImpid()) + .config(mapper.createObjectNode().put("someKey", "someValue")) + .build())) + .build())) + .build(); + } + + private static MedianetBidResponse sampleBidResponseWithoutBidAndWithFledgeConfig(String impId) { + return MedianetBidResponse.builder() + .cur("USD") + .seatbid(emptyList()) + .ext(MedianetBidResponseExt.of(InterestGroupAuctionIntent.builder() + .igs(List.of(InterestGroupAuctionSeller.builder() + .impId(impId) + .config(mapper.createObjectNode().put("someKey", "someValue")) + .build())) + .build())) + .build(); + } + + private static MedianetBidResponse sampleMultiFormatBidResponse(List bids) { + return MedianetBidResponse.builder() .cur("USD") .seatbid(singletonList(SeatBid.builder() .bid(bids) From 24012ed5a279d6b523f8f268229f12f194e8b298 Mon Sep 17 00:00:00 2001 From: "chinmay.b" Date: Thu, 18 Apr 2024 12:22:26 +0530 Subject: [PATCH 2/3] update AuctionIntent field and update tests --- .../org/prebid/server/bidder/medianet/MedianetBidder.java | 3 ++- .../medianet/model/response/MedianetBidResponseExt.java | 4 +++- .../prebid/server/bidder/medianet/MedianetBidderTest.java | 8 ++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/medianet/MedianetBidder.java b/src/main/java/org/prebid/server/bidder/medianet/MedianetBidder.java index e67b92976dc..fd9f78198c0 100644 --- a/src/main/java/org/prebid/server/bidder/medianet/MedianetBidder.java +++ b/src/main/java/org/prebid/server/bidder/medianet/MedianetBidder.java @@ -152,9 +152,10 @@ private static List extractFledge(MedianetBidResponse bidRe return Optional.ofNullable(bidResponse) .map(MedianetBidResponse::getExt) .map(MedianetBidResponseExt::getIgi) - .map(InterestGroupAuctionIntent::getIgs) .orElse(Collections.emptyList()) .stream() + .map(InterestGroupAuctionIntent::getIgs) + .flatMap(Collection::stream) .map(e -> FledgeAuctionConfig.builder().impId(e.getImpId()).config(e.getConfig()).build()) .toList(); } diff --git a/src/main/java/org/prebid/server/bidder/medianet/model/response/MedianetBidResponseExt.java b/src/main/java/org/prebid/server/bidder/medianet/model/response/MedianetBidResponseExt.java index 80497830407..2ce5775704c 100644 --- a/src/main/java/org/prebid/server/bidder/medianet/model/response/MedianetBidResponseExt.java +++ b/src/main/java/org/prebid/server/bidder/medianet/model/response/MedianetBidResponseExt.java @@ -2,8 +2,10 @@ import lombok.Value; +import java.util.List; + @Value(staticConstructor = "of") public class MedianetBidResponseExt { - InterestGroupAuctionIntent igi; + List igi; } diff --git a/src/test/java/org/prebid/server/bidder/medianet/MedianetBidderTest.java b/src/test/java/org/prebid/server/bidder/medianet/MedianetBidderTest.java index 7a0dd76d178..42390ad0f79 100644 --- a/src/test/java/org/prebid/server/bidder/medianet/MedianetBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/medianet/MedianetBidderTest.java @@ -262,12 +262,12 @@ private static MedianetBidResponse sampleBidResponseWithFledgeConfig(Function Date: Wed, 15 May 2024 20:16:54 +0530 Subject: [PATCH 3/3] update depreacted make bids function to return error --- .../server/bidder/medianet/MedianetBidder.java | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/prebid/server/bidder/medianet/MedianetBidder.java b/src/main/java/org/prebid/server/bidder/medianet/MedianetBidder.java index fd9f78198c0..d184aefc297 100644 --- a/src/main/java/org/prebid/server/bidder/medianet/MedianetBidder.java +++ b/src/main/java/org/prebid/server/bidder/medianet/MedianetBidder.java @@ -50,18 +50,8 @@ public Result>> makeHttpRequests(BidRequest bidRequ */ @Override @Deprecated(forRemoval = true) - public final Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { - final MedianetBidResponse bidResponse; - try { - bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), MedianetBidResponse.class); - } catch (DecodeException e) { - return Result.withError(BidderError.badServerResponse(e.getMessage())); - } - - final List errors = new ArrayList<>(); - final List bids = extractBids(httpCall.getRequest().getPayload(), bidResponse, errors); - - return Result.of(bids, errors); + public Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { + return Result.withError(BidderError.generic("Deprecated adapter method invoked")); } @Override