Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package org.prebid.server.bidder.connectad;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.iab.openrtb.request.Banner;
import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Device;
import com.iab.openrtb.request.Format;
import com.iab.openrtb.request.Imp;
import com.iab.openrtb.request.Site;
import com.iab.openrtb.response.Bid;
import com.iab.openrtb.response.BidResponse;
import com.iab.openrtb.response.SeatBid;
import io.vertx.core.MultiMap;
Expand All @@ -31,13 +34,19 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

public class ConnectAdBidder implements Bidder<BidRequest> {

private static final TypeReference<ExtPrebid<?, ExtImpConnectAd>> CONNECTAD_EXT_TYPE_REFERENCE =
new TypeReference<>() {
};
private static final String BIDDER_EXT_KEY = "bidder";
private static final String NETWORK_ID_FIELD = "networkId";
private static final String SITE_ID_FIELD = "siteId";
private static final String HTTPS_PREFIX = "https";

private final String endpointUrl;
Expand All @@ -58,7 +67,7 @@ public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request
for (Imp imp : request.getImp()) {
try {
final ExtImpConnectAd impExt = parseImpExt(imp);
final Imp updatedImp = updateImp(imp, secure, impExt.getSiteId(), impExt.getBidFloor());
final Imp updatedImp = updateImp(imp, secure, impExt);
processedImps.add(updatedImp);
} catch (PreBidException e) {
errors.add(BidderError.badInput(e.getMessage()));
Expand Down Expand Up @@ -91,27 +100,40 @@ private ExtImpConnectAd parseImpExt(Imp imp) {
} catch (IllegalArgumentException e) {
throw new PreBidException("Impression id=%s, has invalid Ext".formatted(imp.getId()));
}
final String siteId = extImpConnectAd.getSiteId();
if (siteId == null) {
if (extImpConnectAd.getSiteId() == null) {
throw new PreBidException("Impression id=%s, has no siteId present".formatted(imp.getId()));
}
return extImpConnectAd;
}

private Imp updateImp(Imp imp, Integer secure, String siteId, BigDecimal bidFloor) {
private static Imp updateImp(Imp imp, int secure, ExtImpConnectAd extImpConnectAd) {
final BigDecimal bidFloor = extImpConnectAd.getBidFloor();
final boolean isValidBidFloor = BidderUtil.isValidPrice(bidFloor);
return imp.toBuilder()
.banner(updateBanner(imp.getBanner()))
.tagid(siteId)
.tagid(String.valueOf(extImpConnectAd.getSiteId()))
.secure(secure)
.bidfloor(isValidBidFloor ? bidFloor : imp.getBidfloor())
.bidfloorcur(isValidBidFloor ? "USD" : imp.getBidfloorcur())
.ext(modifyImpExt(imp.getExt()))
.build();
}

private static ObjectNode modifyImpExt(ObjectNode impExt) {
final ObjectNode modifiedExt = impExt.deepCopy();
final JsonNode bidder = modifiedExt.get(BIDDER_EXT_KEY);
if (bidder.has(NETWORK_ID_FIELD)) {
modifiedExt.set(NETWORK_ID_FIELD, bidder.get(NETWORK_ID_FIELD));
}
if (bidder.has(SITE_ID_FIELD)) {
modifiedExt.set(SITE_ID_FIELD, bidder.get(SITE_ID_FIELD));
}
return modifiedExt;
}

private static Banner updateBanner(Banner banner) {
if (banner == null) {
throw new PreBidException("We need a Banner Object in the request");
return null;
}

if (banner.getW() != null || banner.getH() != null) {
Expand Down Expand Up @@ -153,24 +175,42 @@ private static MultiMap resolveHeaders(Device device) {
public final Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
try {
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
return Result.withValues(extractBids(bidResponse));
return Result.withValues(extractBids(bidResponse, bidRequest));
} catch (DecodeException | PreBidException e) {
return Result.withError(BidderError.badServerResponse(e.getMessage()));
}
}

private List<BidderBid> extractBids(BidResponse bidResponse) {
private List<BidderBid> extractBids(BidResponse bidResponse, BidRequest bidRequest) {
if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) {
return Collections.emptyList();
}

final Map<String, Imp> impIdToImp = bidRequest.getImp().stream()
.collect(Collectors.toMap(Imp::getId, Function.identity()));

return bidResponse.getSeatbid().stream()
.filter(Objects::nonNull)
.map(SeatBid::getBid)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.filter(Objects::nonNull)
.map(bid -> BidderBid.of(bid, BidType.banner, bidResponse.getCur()))
.map(bid -> BidderBid.of(bid, getBidType(bid, impIdToImp), bidResponse.getCur()))
.toList();
}

private static BidType getBidType(Bid bid, Map<String, Imp> impIdToImp) {
final Integer mType = bid.getMtype();
if (mType == null) {
return BidderUtil.getBidType(bid, impIdToImp);
}

return switch (mType) {
case 1 -> BidType.banner;
case 2 -> BidType.video;
case 3 -> BidType.audio;
case 4 -> BidType.xNative;
default -> BidType.banner;
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
public class ExtImpConnectAd {

@JsonProperty("networkId")
String networkId;
Integer networkId;

@JsonProperty("siteId")
String siteId;
Integer siteId;

@JsonProperty("bidfloor")
BigDecimal bidFloor;
Expand Down
13 changes: 10 additions & 3 deletions src/main/resources/bidder-config/connectad.yaml
Comment thread
rtuschkany marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,25 @@ adapters:
connectad:
# Please uncomment the appropriate endpoint URL for your datacenter
# Europe
endpoint: "http://bidder.connectad.io/API?src=pbs"
endpoint: "https://bidder.connectad.io/API?src=pbs"
ortb-version: "2.6"
# North/South America
# endpoint: "http://bidder-us.connectad.io/API?src=pbs"
# endpoint: "https://bidder-us.connectad.io/API?src=pbs"
# APAC
# endpoint: "http://bidder-apac.connectad.io/API?src=pbs"
# endpoint: "https://bidder-apac.connectad.io/API?src=pbs"
endpoint-compression: gzip
meta-info:
maintainer-email: support@connectad.io
app-media-types:
- banner
- video
- native
- audio
site-media-types:
- banner
- video
- native
- audio
supported-vendors:
vendor-id: 138
usersync:
Expand Down
8 changes: 6 additions & 2 deletions src/main/resources/static/bidder-params/connectad.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "ConnectAd S2S dapter Params",
"title": "ConnectAd S2S Adapter Params",
"description": "A schema which validates params accepted by the ConnectAd Adapter",
"type": "object",
"properties": {
Expand All @@ -20,7 +20,11 @@
},
"bidfloor": {
"type": "number",
"description": "Requests Floorprice"
"description": "Requested Floorprice"
},
"endpointUrl": {
"type": "string",
"description": "Client-side only (Prebid.js): override the bid endpoint URL for testing or a custom datacenter. Ignored by Prebid Server, which always uses its configured endpoint."
}
},
"required": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public void makeHttpRequestsShouldReturnErrorWhenImpExtCouldNotBeParsed() {
final BidRequest bidRequest = BidRequest.builder()
.imp(singletonList(Imp.builder()
.id("123")
.banner(Banner.builder().w(300).h(250).build())
.ext(mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode())))
.build()))
.build();
Expand Down Expand Up @@ -109,12 +110,15 @@ public void makeBidsShouldReturnEmptyListWhenBidResponseSeatBidIsNull() throws J
@Test
public void makeBidsShouldReturnBannerBid() throws JsonProcessingException {
// given
final BidRequest bidRequest = BidRequest.builder()
.imp(singletonList(Imp.builder().id("123").banner(Banner.builder().build()).build()))
.build();
final BidderCall<BidRequest> httpCall = givenHttpCall(
BidRequest.builder().imp(singletonList(Imp.builder().id("123").build())).build(),
bidRequest,
mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.impid("123"))));

// when
final Result<List<BidderBid>> result = target.makeBids(httpCall, null);
final Result<List<BidderBid>> result = target.makeBids(httpCall, bidRequest);

// then
assertThat(result.getErrors()).isEmpty();
Expand Down Expand Up @@ -146,7 +150,7 @@ public void makeHttpRequestsShouldReturnErrorIfImpExtHasNoSiteId() {
impBuilder -> impBuilder
.id("123")
.ext(mapper.valueToTree(ExtPrebid.of(null,
ExtImpConnectAd.of("12", null, BigDecimal.ONE)))));
ExtImpConnectAd.of(12, null, BigDecimal.ONE)))));
// when
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);

Expand All @@ -164,7 +168,7 @@ public void impSecureShouldBeOneIfSitePageStartsFromHttps() {
impBuilder -> impBuilder
.id("123")
.ext(mapper.valueToTree(ExtPrebid.of(null,
ExtImpConnectAd.of("12", "1", BigDecimal.ONE)))));
ExtImpConnectAd.of(12, 1, BigDecimal.ONE)))));
// when
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);

Expand All @@ -180,6 +184,32 @@ public void impSecureShouldBeOneIfSitePageStartsFromHttps() {
.containsOnly(1);
}

@Test
public void makeHttpRequestsShouldPropagateSiteIdAndNetworkId() {
// given
final BidRequest bidRequest = givenBidRequest(
impBuilder -> impBuilder
.id("123")
.ext(mapper.valueToTree(ExtPrebid.of(null,
ExtImpConnectAd.of(12345, 67890, BigDecimal.ONE)))));
// when
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);

// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue())
.hasSize(1)
.extracting(HttpRequest::getPayload)
.flatExtracting(BidRequest::getImp)
.hasSize(1)
.first()
.satisfies(imp -> {
assertThat(imp.getTagid()).isEqualTo("67890");
assertThat(imp.getExt().get("networkId").asInt()).isEqualTo(12345);
assertThat(imp.getExt().get("siteId").asInt()).isEqualTo(67890);
});
}

private static BidRequest givenBidRequest(
Function<BidRequest.BidRequestBuilder, BidRequest.BidRequestBuilder> bidRequestCustomizer,
Function<Imp.ImpBuilder, Imp.ImpBuilder> impCustomizer) {
Expand All @@ -202,7 +232,7 @@ private static Imp givenImp(Function<Imp.ImpBuilder, Imp.ImpBuilder> impCustomiz
.w(14)
.h(15).build())
.ext(mapper.valueToTree(ExtPrebid.of(null,
ExtImpConnectAd.of("12", "12", BigDecimal.ONE)))))
ExtImpConnectAd.of(12, 12, BigDecimal.ONE)))))
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,23 @@
"imp": [
{
"id": "imp_id",
"secure": 1,
"secure": 0,
"banner": {
"w": 300,
"h": 250
},
"tagid": "15",
"bidfloor": 14.7,
"bidfloorcur": "USD",
"secure": 0,
"ext": {
"tid": "${json-unit.any-string}",
"bidder": {
"networkId": "12",
"siteId": "15",
"bidfloor": 14.7
}
},
"networkId": "12",
"siteId": "15"
}
}
],
Expand All @@ -45,9 +46,7 @@
"USD"
],
"regs": {
"ext": {
"gdpr": 0
}
"gdpr": 0
},
"ext": {
"prebid": {
Expand Down
Loading