Skip to content

Commit

Permalink
Yieldlab: Add Digital Service Act (DSA) support (#3011)
Browse files Browse the repository at this point in the history
  • Loading branch information
william-harris authored Mar 26, 2024
1 parent 591e2f6 commit a475313
Show file tree
Hide file tree
Showing 4 changed files with 322 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.fasterxml.jackson.core.JsonProcessingException;
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.App;
import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Device;
Expand All @@ -14,6 +16,9 @@
import io.netty.handler.codec.http.HttpHeaderValues;
import io.vertx.core.MultiMap;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
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;
Expand All @@ -23,11 +28,15 @@
import org.prebid.server.bidder.model.BidderError;
import org.prebid.server.bidder.model.HttpRequest;
import org.prebid.server.bidder.model.Result;
import org.prebid.server.bidder.yieldlab.model.YieldlabDigitalServicesActResponse;
import org.prebid.server.bidder.yieldlab.model.YieldlabResponse;
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.request.ExtRegs;
import org.prebid.server.proto.openrtb.ext.request.ExtRegsDsa;
import org.prebid.server.proto.openrtb.ext.request.ExtRegsDsaTransparency;
import org.prebid.server.proto.openrtb.ext.request.ExtRequest;
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid;
import org.prebid.server.proto.openrtb.ext.request.ExtUser;
Expand All @@ -41,13 +50,16 @@
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

public class YieldlabBidder implements Bidder<Void> {

private static final Logger logger = LoggerFactory.getLogger(YieldlabBidder.class);
private static final TypeReference<ExtPrebid<?, ExtImpYieldlab>> YIELDLAB_EXT_TYPE_REFERENCE =
new TypeReference<>() {
};
Expand All @@ -58,6 +70,9 @@ public class YieldlabBidder implements Bidder<Void> {
private static final String CREATIVE_ID = "%s%s%s";
private static final String AD_SOURCE_BANNER = "<script src=\"%s\"></script>";
private static final String AD_SOURCE_URL = "https://ad.yieldlab.net/d/%s/%s/%s?%s";
private static final String TRANSPARENCY_TEMPLATE = "%s~%s";
private static final String TRANSPARENCY_TEMPLATE_PARAMS_DELIMITER = "_";
private static final String TRANSPARENCY_TEMPLATE_DELIMITER = "~~";
private static final String VAST_MARKUP = """
<VAST version="2.0"><Ad id="%s"><Wrapper>
<AdSystem>Yieldlab</AdSystem>
Expand Down Expand Up @@ -189,6 +204,8 @@ private String makeUrl(ExtImpYieldlab extImpYieldlab, BidRequest request) {
uriBuilder.addParameter("consent", consent);
}

extractDsaRequestParamsFromBidRequest(request).forEach(uriBuilder::addParameter);

return uriBuilder.toString();
}

Expand Down Expand Up @@ -231,6 +248,63 @@ private static String getConsentParameter(User user) {
return ObjectUtils.defaultIfNull(consent, "");
}

private static Map<String, String> extractDsaRequestParamsFromBidRequest(BidRequest request) {
return Optional.ofNullable(request.getRegs())
.map(Regs::getExt)
.map(ExtRegs::getDsa)
.map(YieldlabBidder::extractDsaRequestParamsFromDsaRegsExtension)
.orElse(Collections.emptyMap());
}

private static Map<String, String> extractDsaRequestParamsFromDsaRegsExtension(final ExtRegsDsa dsa) {
final Map<String, String> dsaRequestParams = new HashMap<>();

if (dsa.getDsaRequired() != null) {
dsaRequestParams.put("dsarequired", dsa.getDsaRequired().toString());
}

if (dsa.getPubRender() != null) {
dsaRequestParams.put("dsapubrender", dsa.getPubRender().toString());
}

if (dsa.getDataToPub() != null) {
dsaRequestParams.put("dsadatatopub", dsa.getDataToPub().toString());
}

final List<ExtRegsDsaTransparency> dsaTransparency = dsa.getTransparency();
if (CollectionUtils.isNotEmpty(dsaTransparency)) {
final String encodedTransparencies = encodeTransparenciesAsString(dsaTransparency);
if (StringUtils.isNotBlank(encodedTransparencies)) {
dsaRequestParams.put("dsatransparency", encodedTransparencies);
}
}

return dsaRequestParams;
}

private static String encodeTransparenciesAsString(List<ExtRegsDsaTransparency> transparencies) {
return transparencies.stream()
.filter(YieldlabBidder::isTransparencyValid)
.map(YieldlabBidder::encodeTransparency)
.collect(Collectors.joining(TRANSPARENCY_TEMPLATE_DELIMITER));
}

private static boolean isTransparencyValid(ExtRegsDsaTransparency transparency) {
return StringUtils.isNotBlank(transparency.getDomain())
&& transparency.getDsaParams() != null
&& CollectionUtils.isNotEmpty(transparency.getDsaParams());
}

private static String encodeTransparency(ExtRegsDsaTransparency transparency) {
return TRANSPARENCY_TEMPLATE.formatted(transparency.getDomain(),
encodeTransparencyParams(transparency.getDsaParams()));
}

private static String encodeTransparencyParams(List<Integer> dsaParams) {
return dsaParams.stream().map(Objects::toString).collect(Collectors.joining(
TRANSPARENCY_TEMPLATE_PARAMS_DELIMITER));
}

private static MultiMap resolveHeaders(Site site, Device device, User user) {
final MultiMap headers = MultiMap.caseInsensitiveMultiMap()
.add(HttpUtil.ACCEPT_HEADER, HttpHeaderValues.APPLICATION_JSON);
Expand Down Expand Up @@ -339,7 +413,8 @@ private Bid.BidBuilder addBidParams(YieldlabResponse yieldlabResponse, BidReques
.dealid(resolveNumberParameter(yieldlabResponse.getPid()))
.crid(makeCreativeId(bidRequest, yieldlabResponse, matchedExtImp))
.w(resolveSizeParameter(yieldlabResponse.getAdSize(), true))
.h(resolveSizeParameter(yieldlabResponse.getAdSize(), false));
.h(resolveSizeParameter(yieldlabResponse.getAdSize(), false))
.ext(resolveExtParameter(yieldlabResponse));

return updatedBid;
}
Expand Down Expand Up @@ -408,4 +483,21 @@ private String makeNurl(BidRequest bidRequest, ExtImpYieldlab extImpYieldlab, Yi
yieldlabResponse.getAdSize(),
uriBuilder.toString().replace("?", ""));
}

private ObjectNode resolveExtParameter(YieldlabResponse yieldlabResponse) {
final YieldlabDigitalServicesActResponse dsa = yieldlabResponse.getDsa();
if (dsa == null) {
return null;
}
final ObjectNode ext = mapper.mapper().createObjectNode();
final JsonNode dsaNode;
try {
dsaNode = mapper.mapper().valueToTree(dsa);
} catch (IllegalArgumentException e) {
logger.error("Failed to serialize DSA object for adslot {}", yieldlabResponse.getId(), e);
return null;
}
ext.set("dsa", dsaNode);
return ext;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.prebid.server.bidder.yieldlab.model;

import lombok.AllArgsConstructor;
import lombok.Value;

import java.util.List;

@AllArgsConstructor(staticName = "of")
@Value(staticConstructor = "of")
public class YieldlabDigitalServicesActResponse {

String behalf;

String paid;

Integer adrender;

List<Transparency> transparency;

@AllArgsConstructor(staticName = "of")
@Value(staticConstructor = "of")
public static class Transparency {
String domain;
List<Integer> dsaparams;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@ public class YieldlabResponse {
Integer did;

String pvid;

YieldlabDigitalServicesActResponse dsa;
}
Loading

0 comments on commit a475313

Please sign in to comment.