Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 43 additions & 21 deletions src/main/java/org/prebid/server/bidder/visx/VisxBidder.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -18,21 +21,28 @@
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;

import java.util.Collection;
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<BidRequest> {

private static final String DEFAULT_REQUEST_CURRENCY = "USD";
private static final Set<String> SUPPORTED_BID_TYPES_TEXTUAL = Set.of("banner", "video");

private static final TypeReference<ExtPrebid<ExtBidPrebid, ?>> BID_EXT_TYPE_REFERENCE = new TypeReference<>() {
};

private final String endpointUrl;
private final JacksonMapper mapper;

Expand All @@ -43,20 +53,28 @@ public VisxBidder(String endpointUrl, JacksonMapper mapper) {

@Override
public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request) {
return Result.withValue(makeRequest(request));
}

private HttpRequest<BidRequest> 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<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
try {
Expand All @@ -80,14 +98,14 @@ private List<BidderBid> 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) {
Expand All @@ -105,20 +123,24 @@ private static Bid toBid(VisxBid visxBid, String id) {
.build();
}

private static BidType getBidType(ObjectNode bidExt, String impId, List<Imp> imps) {
private BidType getBidType(ObjectNode bidExt, String impId, List<Imp> 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<Imp> imps) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@
public class VisxResponse {

List<VisxSeatBid> seatbid;

String cur;
}
2 changes: 1 addition & 1 deletion src/main/resources/bidder-config/visx.yaml
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
61 changes: 54 additions & 7 deletions src/test/java/org/prebid/server/bidder/visx/VisxBidderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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<List<HttpRequest<BidRequest>>> 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<List<HttpRequest<BidRequest>>> 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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -314,7 +361,7 @@ private static Imp givenImp(UnaryOperator<Imp.ImpBuilder> impCustomizer) {

private static VisxResponse givenVisxResponse(UnaryOperator<VisxBid.VisxBidBuilder> 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<Bid.BidBuilder> bidCustomizer) {
Expand Down
Loading