diff --git a/src/main/java/org/prebid/server/bidder/eplanning/EplanningBidder.java b/src/main/java/org/prebid/server/bidder/eplanning/EplanningBidder.java index aab1f5b7e86..09c607d7451 100644 --- a/src/main/java/org/prebid/server/bidder/eplanning/EplanningBidder.java +++ b/src/main/java/org/prebid/server/bidder/eplanning/EplanningBidder.java @@ -8,11 +8,15 @@ import com.iab.openrtb.request.Format; import com.iab.openrtb.request.Imp; import com.iab.openrtb.request.Site; +import com.iab.openrtb.request.Source; +import com.iab.openrtb.request.SupplyChain; +import com.iab.openrtb.request.SupplyChainNode; import com.iab.openrtb.request.User; import com.iab.openrtb.response.Bid; import io.vertx.core.MultiMap; import io.vertx.core.http.HttpMethod; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.http.client.utils.URIBuilder; import org.prebid.server.bidder.Bidder; @@ -29,6 +33,7 @@ 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.request.ExtSource; import org.prebid.server.proto.openrtb.ext.request.eplanning.ExtImpEplanning; import org.prebid.server.proto.openrtb.ext.response.BidType; import org.prebid.server.util.HttpUtil; @@ -45,6 +50,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -269,6 +275,12 @@ private String resolveRequestUri(BidRequest request, List requestsString uriBuilder.addParameter("app", REQUEST_TARGET_INVENTORY); } + String schain = getSchainParameter(request.getSource()); + if (schain != null) { + schain = schain.replace(" ", "%20"); + uriBuilder.addParameter("sch", schain); + } + return uriBuilder.toString(); } @@ -280,6 +292,53 @@ private static URL parseUrl(String url) { } } + private String getSchainParameter(Source source) { + return Optional.ofNullable(source) + .map(Source::getExt) + .map(ExtSource::getSchain) + .map(this::resolveSupplyChain) + .orElse(null); + } + + private String resolveSupplyChain(SupplyChain schain) { + final List nodes = schain.getNodes(); + if (CollectionUtils.isEmpty(nodes) || nodes.size() > 2) { + return null; + } + + final StringBuilder schainBuilder = new StringBuilder(); + + schainBuilder.append(schain.getVer()); + schainBuilder.append(","); + schainBuilder.append(ObjectUtils.defaultIfNull(schain.getComplete(), 0)); + for (SupplyChainNode node : schain.getNodes()) { + schainBuilder.append("!"); + schainBuilder.append(StringUtils.defaultString(node.getAsi())); + schainBuilder.append(","); + + schainBuilder.append(StringUtils.defaultString(node.getSid())); + schainBuilder.append(","); + + schainBuilder.append(node.getHp() != null ? node.getHp() : StringUtils.EMPTY); + schainBuilder.append(","); + + schainBuilder.append(StringUtils.defaultString(node.getRid())); + schainBuilder.append(","); + + schainBuilder.append(StringUtils.defaultString(node.getName())); + schainBuilder.append(","); + + schainBuilder.append(StringUtils.defaultString(node.getDomain())); + schainBuilder.append(","); + + schainBuilder.append(node.getExt() == null + ? StringUtils.EMPTY + : mapper.encodeToString(node.getExt())); + } + + return schainBuilder.toString(); + } + /** * Converts response to {@link List} of {@link BidderBid}s with {@link List} of errors. * Handles cases when response status is different to OK 200. diff --git a/src/test/java/org/prebid/server/bidder/eplanning/EplanningBidderTest.java b/src/test/java/org/prebid/server/bidder/eplanning/EplanningBidderTest.java index 00ba9278765..8b94f14c737 100644 --- a/src/test/java/org/prebid/server/bidder/eplanning/EplanningBidderTest.java +++ b/src/test/java/org/prebid/server/bidder/eplanning/EplanningBidderTest.java @@ -8,6 +8,9 @@ import com.iab.openrtb.request.Format; import com.iab.openrtb.request.Imp; import com.iab.openrtb.request.Site; +import com.iab.openrtb.request.Source; +import com.iab.openrtb.request.SupplyChain; +import com.iab.openrtb.request.SupplyChainNode; import com.iab.openrtb.request.User; import com.iab.openrtb.response.Bid; import io.netty.handler.codec.http.HttpHeaderValues; @@ -25,6 +28,7 @@ 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.request.ExtSource; import org.prebid.server.proto.openrtb.ext.request.eplanning.ExtImpEplanning; import org.prebid.server.proto.openrtb.ext.response.BidType; import org.prebid.server.util.HttpUtil; @@ -35,6 +39,7 @@ import java.util.function.Function; import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static java.util.function.Function.identity; import static org.assertj.core.api.Assertions.assertThat; @@ -413,6 +418,176 @@ public void makeHttpRequestsShouldSetCorrectUriWithApp() { + "appn=appName&appid=id&ifa=ifa&app=1"); } + @Test + public void makeHttpRequestsShouldNotAppendSchainIfSourceIsNull() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(givenImp(identity()))) + .source(null) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + assertThat(result.getValue()).hasSize(1); + final String uri = result.getValue().get(0).getUri(); + assertThat(uri).doesNotContain("sch="); + } + + @Test + public void makeHttpRequestsShouldNotAppendSchainIfExtIsNull() { + // given + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(givenImp(identity()))) + .source(Source.builder().ext(null).build()) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + final String uri = result.getValue().get(0).getUri(); + assertThat(uri).doesNotContain("sch="); + } + + @Test + public void makeHttpRequestsShouldNotAppendSchainIfSchainIsNull() { + // given + final Source source = Source.builder() + .ext(ExtSource.of(null)) + .build(); + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(givenImp(identity()))) + .source(source) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + final String uri = result.getValue().get(0).getUri(); + assertThat(uri).doesNotContain("sch="); + } + + @Test + public void makeHttpRequestsShouldNotAppendSchainIfNoNodes() { + // given + final SupplyChain supplyChain = SupplyChain.of(1, emptyList(), "1.0", null); + final Source source = Source.builder() + .ext(ExtSource.of(supplyChain)) + .build(); + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(givenImp(identity()))) + .source(source) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + final String uri = result.getValue().get(0).getUri(); + assertThat(uri).doesNotContain("sch="); + } + + @Test + public void makeHttpRequestsShouldNotAppendSchainIfNodeCountIsGreaterThan2() { + // given + final List nodes = asList( + SupplyChainNode.of("asi1", "sid1", "rid1", "name1", "domain1", 1, null), + SupplyChainNode.of("asi2", "sid2", "rid2", "name2", "domain2", 1, null), + SupplyChainNode.of("asi3", "sid3", "rid3", "name3", "domain3", 1, null) + ); + final SupplyChain supplyChain = SupplyChain.of(1, nodes, "1.0", null); + final Source source = Source.builder() + .ext(ExtSource.of(supplyChain)) + .build(); + + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(givenImp(identity()))) + .source(source) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + final String uri = result.getValue().get(0).getUri(); + assertThat(uri).doesNotContain("sch="); + } + + @Test + public void makeHttpRequestsShouldAppendSchainForUpToTwoNodes() { + // given + final List nodes = asList( + SupplyChainNode.of("asi1", "sid1", "rid1", "name1", "domain1", 1, null), + SupplyChainNode.of("asi2", "sid2", null, null, "domain2", 1, null) + ); + final SupplyChain supplyChain = SupplyChain.of(1, nodes, "1.0", null); + final Source source = Source.builder() + .ext(ExtSource.of(supplyChain)) + .build(); + + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(givenImp(identity()))) + .source(source) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + assertThat(result.getErrors()).isEmpty(); + final String uri = result.getValue().get(0).getUri(); + assertThat(uri) + .contains("sch=") + .contains("%21asi1%2Csid1%2C1%2Crid1%2Cname1%2Cdomain1%2C") + .contains("%21asi2%2Csid2%2C1%2C%2C%2Cdomain2%2C"); + } + + @Test + public void makeHttpRequestsShouldUrlEncodeSchainFieldsCorrectly() { + // given + final List nodes = singletonList( + SupplyChainNode.of( + "a si", + "s/id", + null, + "r:id", + "na me", + 1, + jacksonMapper.mapper().createObjectNode().put("k", "v val")) + ); + final SupplyChain supplyChain = SupplyChain.of(0, nodes, "1.0", null); + final Source source = Source.builder() + .ext(ExtSource.of(supplyChain)) + .build(); + + final BidRequest bidRequest = BidRequest.builder() + .imp(singletonList(givenImp(identity()))) + .source(source) + .build(); + + // when + final Result>> result = target.makeHttpRequests(bidRequest); + + // then + final String uri = result.getValue().get(0).getUri(); + + assertThat(uri).contains("&sch="); + assertThat(uri).contains("1.0%2C0"); + assertThat(uri).contains("%21a%2520si"); + assertThat(uri).contains("s%2Fid"); + assertThat(uri).contains("r%3Aid"); + assertThat(uri).contains("na%2520me"); + assertThat(uri).contains("%7B%22k%22%3A%22v%2520val%22%7D"); + } + @Test public void makeBidsShouldReturnErrorIfResponseBodyCouldNotBeParsed() { // given