diff --git a/src/main/java/org/prebid/server/bidder/visx/VisxBidder.java b/src/main/java/org/prebid/server/bidder/visx/VisxBidder.java index e7f91b12717..49e64645206 100644 --- a/src/main/java/org/prebid/server/bidder/visx/VisxBidder.java +++ b/src/main/java/org/prebid/server/bidder/visx/VisxBidder.java @@ -1,11 +1,14 @@ package org.prebid.server.bidder.visx; -import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.node.ObjectNode; import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Device; import com.iab.openrtb.request.Imp; import com.iab.openrtb.response.Bid; +import io.vertx.core.MultiMap; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.prebid.server.bidder.Bidder; import org.prebid.server.bidder.model.BidderBid; import org.prebid.server.bidder.model.BidderCall; @@ -18,7 +21,10 @@ 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.ExtPrebid; 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.util.BidderUtil; import org.prebid.server.util.HttpUtil; @@ -26,6 +32,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.Set; public class VisxBidder implements Bidder { @@ -33,6 +40,9 @@ public class VisxBidder implements Bidder { private static final String DEFAULT_REQUEST_CURRENCY = "USD"; private static final Set SUPPORTED_BID_TYPES_TEXTUAL = Set.of("banner", "video"); + private static final TypeReference> BID_EXT_TYPE_REFERENCE = new TypeReference<>() { + }; + private final String endpointUrl; private final JacksonMapper mapper; @@ -43,20 +53,28 @@ public VisxBidder(String endpointUrl, JacksonMapper mapper) { @Override public Result>> makeHttpRequests(BidRequest request) { - return Result.withValue(makeRequest(request)); - } - - private HttpRequest makeRequest(BidRequest bidRequest) { - final BidRequest outgoingRequest = modifyRequest(bidRequest); - return BidderUtil.defaultRequest(outgoingRequest, endpointUrl, mapper); + final BidRequest outgoingRequest = modifyRequest(request); + return Result.withValue( + BidderUtil.defaultRequest(outgoingRequest, makeHeaders(request.getDevice()), endpointUrl, mapper)); } - private BidRequest modifyRequest(BidRequest bidRequest) { + private static BidRequest modifyRequest(BidRequest bidRequest) { return CollectionUtils.isEmpty(bidRequest.getCur()) ? bidRequest.toBuilder().cur(Collections.singletonList(DEFAULT_REQUEST_CURRENCY)).build() : bidRequest; } + private static MultiMap makeHeaders(Device device) { + final MultiMap headers = HttpUtil.headers(); + + if (device != null) { + HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, device.getIp()); + HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, device.getIpv6()); + } + + return headers; + } + @Override public Result> makeBids(BidderCall httpCall, BidRequest bidRequest) { try { @@ -80,14 +98,14 @@ private List bidsFromResponse(BidRequest bidRequest, VisxResponse vis .map(VisxSeatBid::getBid) .filter(Objects::nonNull) .flatMap(Collection::stream) - .map(visxBid -> toBidderBid(bidRequest, visxBid)) + .map(visxBid -> toBidderBid(bidRequest, visxBid, visxResponse.getCur())) .toList(); } - private BidderBid toBidderBid(BidRequest bidRequest, VisxBid visxBid) { + private BidderBid toBidderBid(BidRequest bidRequest, VisxBid visxBid, String currency) { final Bid bid = toBid(visxBid, bidRequest.getId()); final BidType bidType = getBidType(bid.getExt(), bid.getImpid(), bidRequest.getImp()); - return BidderBid.of(bid, bidType, null); + return BidderBid.of(bid, bidType, StringUtils.defaultIfBlank(currency, null)); } private static Bid toBid(VisxBid visxBid, String id) { @@ -105,20 +123,24 @@ private static Bid toBid(VisxBid visxBid, String id) { .build(); } - private static BidType getBidType(ObjectNode bidExt, String impId, List imps) { + private BidType getBidType(ObjectNode bidExt, String impId, List imps) { final BidType extBidType = getBidTypeFromExt(bidExt); return extBidType != null ? extBidType : getBidTypeFromImp(impId, imps); } - private static BidType getBidTypeFromExt(ObjectNode bidExt) { - final JsonNode mediaTypeNode = bidExt != null ? bidExt.at("/prebid/meta/mediaType") : null; - final String bidTypeTextual = mediaTypeNode != null && mediaTypeNode.isTextual() - ? mediaTypeNode.asText() - : null; - - return bidTypeTextual != null && SUPPORTED_BID_TYPES_TEXTUAL.contains(bidTypeTextual) - ? BidType.valueOf(bidTypeTextual) - : null; + private BidType getBidTypeFromExt(ObjectNode bidExt) { + try { + return Optional.ofNullable(bidExt) + .map(ext -> mapper.mapper().convertValue(bidExt, BID_EXT_TYPE_REFERENCE)) + .map(ExtPrebid::getPrebid) + .map(ExtBidPrebid::getMeta) + .map(ExtBidPrebidMeta::getMediaType) + .filter(SUPPORTED_BID_TYPES_TEXTUAL::contains) + .map(BidType::valueOf) + .orElse(null); + } catch (IllegalArgumentException e) { + return null; + } } private static BidType getBidTypeFromImp(String impId, List imps) { diff --git a/src/main/java/org/prebid/server/bidder/visx/model/VisxResponse.java b/src/main/java/org/prebid/server/bidder/visx/model/VisxResponse.java index 0022d773875..df156830167 100644 --- a/src/main/java/org/prebid/server/bidder/visx/model/VisxResponse.java +++ b/src/main/java/org/prebid/server/bidder/visx/model/VisxResponse.java @@ -8,4 +8,6 @@ public class VisxResponse { List seatbid; + + String cur; } diff --git a/src/main/resources/bidder-config/visx.yaml b/src/main/resources/bidder-config/visx.yaml index 9d99f8a31f0..df8384fc6c1 100644 --- a/src/main/resources/bidder-config/visx.yaml +++ b/src/main/resources/bidder-config/visx.yaml @@ -1,6 +1,6 @@ adapters: visx: - endpoint: https://t.visx.net/s2s_bid?wrapperType=s2s_prebid_java + endpoint: https://t.visx.net/s2s_bid?wrapperType=s2s_prebid_standard:0.1.2 meta-info: maintainer-email: supply.partners@yoc.com app-media-types: diff --git a/src/test/java/org/prebid/server/bidder/visx/VisxBidderTest.java b/src/test/java/org/prebid/server/bidder/visx/VisxBidderTest.java index cd11937bd94..d3640d344b7 100644 --- a/src/test/java/org/prebid/server/bidder/visx/VisxBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/visx/VisxBidderTest.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.node.TextNode; import com.iab.openrtb.request.Banner; import com.iab.openrtb.request.BidRequest; +import com.iab.openrtb.request.Device; import com.iab.openrtb.request.Imp; import com.iab.openrtb.request.Video; import com.iab.openrtb.response.Bid; @@ -22,16 +23,19 @@ import org.prebid.server.bidder.visx.model.VisxSeatBid; import org.prebid.server.proto.openrtb.ext.ExtPrebid; import org.prebid.server.proto.openrtb.ext.request.visx.ExtImpVisx; +import org.prebid.server.util.HttpUtil; import java.math.BigDecimal; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.function.UnaryOperator; import static java.util.Collections.singletonList; import static java.util.function.UnaryOperator.identity; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.tuple; import static org.prebid.server.proto.openrtb.ext.response.BidType.banner; import static org.prebid.server.proto.openrtb.ext.response.BidType.video; @@ -68,6 +72,48 @@ public void makeHttpRequestsShouldNotModifyIncomingRequest() { .containsExactly(bidRequest); } + @Test + public void makeHttpRequestsShouldAddIp() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(Imp.builder() + .ext(mapper.valueToTree(ExtPrebid.of(null, + ExtImpVisx.of(123, Arrays.asList(10, 20))))) + .build())) + .device(Device.builder().ip("someIp").ipv6("ipv6").build()) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getValue()) + .flatExtracting(res -> res.getHeaders().entries()) + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .contains(tuple(HttpUtil.X_FORWARDED_FOR_HEADER.toString(), "someIp")); + } + + @Test + public void makeHttpRequestsShouldAddIpv6IfIpIsNotPresent() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(Imp.builder() + .ext(mapper.valueToTree(ExtPrebid.of(null, + ExtImpVisx.of(123, Arrays.asList(10, 20))))) + .build())) + .device(Device.builder().ipv6("ipv6").build()) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getValue()) + .flatExtracting(res -> res.getHeaders().entries()) + .extracting(Map.Entry::getKey, Map.Entry::getValue) + .contains(tuple(HttpUtil.X_FORWARDED_FOR_HEADER.toString(), "ipv6")); + } + @Test public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() { // given @@ -122,7 +168,7 @@ public void makeBidsShouldReturnBidWithTypeBannerIfBannerIsPresent() throws Json // then assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()).containsExactly( - BidderBid.of(Bid.builder().id("id").impid("123").build(), banner, null)); + BidderBid.of(Bid.builder().id("id").impid("123").build(), banner, "USD")); } @Test @@ -138,7 +184,7 @@ public void makeBidsShouldReturnBidWithTypeBannerIfVideoIsPresentAndBannerIsAbse // then assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()).containsExactly( - BidderBid.of(Bid.builder().id("id").impid("123").build(), video, null)); + BidderBid.of(Bid.builder().id("id").impid("123").build(), video, "USD")); } @Test @@ -194,7 +240,7 @@ public void makeBidsShouldFavourBidExtMediaTypeToImpMediaTypeWhenPresent() throw // then assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()) - .containsExactly(BidderBid.of(givenBid(identity()), banner, null)); + .containsExactly(BidderBid.of(givenBid(identity()), banner, "USD")); } @Test @@ -213,7 +259,7 @@ public void makeBidsShouldReturnImpMediaTypeWhenBidExtMediaTypeIsAbsent() throws // then assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()).containsExactly( - BidderBid.of(givenBid(bidBuilder -> bidBuilder.ext(null)), video, null)); + BidderBid.of(givenBid(bidBuilder -> bidBuilder.ext(null)), video, "USD")); } @Test @@ -235,7 +281,7 @@ public void makeBidsShouldReturnImpMediaTypeWhenBidExtMediaTypeIsInvalid() throw // then assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()).containsExactly( - BidderBid.of(givenBid(bidBuilder -> bidBuilder.ext(givenBidExt("123"))), video, null)); + BidderBid.of(givenBid(bidBuilder -> bidBuilder.ext(givenBidExt("123"))), video, "USD")); } @Test @@ -289,7 +335,8 @@ public void makeBidsShouldReturnCorrectBidderBid() throws JsonProcessingExceptio .h(100) .adomain(singletonList("adomain")) .build(), - video, null); + video, + "USD"); assertThat(result.getErrors()).isEmpty(); assertThat(result.getValue()).containsExactly(expected); @@ -314,7 +361,7 @@ private static Imp givenImp(UnaryOperator impCustomizer) { private static VisxResponse givenVisxResponse(UnaryOperator bidCustomizer, String seat) { return VisxResponse.of(singletonList(VisxSeatBid.of( - singletonList(bidCustomizer.apply(VisxBid.builder()).build()), seat))); + singletonList(bidCustomizer.apply(VisxBid.builder()).build()), seat)), "USD"); } private static Bid givenBid(UnaryOperator bidCustomizer) {