-
Notifications
You must be signed in to change notification settings - Fork 187
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Yieldlab: Add Digital Service Act (DSA) support #3011
Changes from all commits
cc0cca6
edf9d9f
3c57c36
35f54c5
e07cb0b
0ffb3a7
358acf0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, i don't see anything similar in GO for Does PBS-Java need to pass this debug value? Just out of interest I removed the debug part and all tests passed: Same question about and
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As far as we are concerned, it is not needed no. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As a courtesy I can fix this, but this wasn't written by us and is also out of scope. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Implementing this breaks the tests There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This code doesn't handle There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is out of scope, and I don't intend to change it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, I don't see implementation of this part:
Will it be added later? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, I don't see implementation of this part:
Will it be added later? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Out of scope, but.... Can i ask you to extract There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm sorry, but I must decline. Like you said it is out of scope. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
@@ -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; | ||
|
@@ -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; | ||
|
@@ -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<>() { | ||
}; | ||
|
@@ -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> | ||
|
@@ -189,6 +204,8 @@ private String makeUrl(ExtImpYieldlab extImpYieldlab, BidRequest request) { | |
uriBuilder.addParameter("consent", consent); | ||
} | ||
|
||
extractDsaRequestParamsFromBidRequest(request).forEach(uriBuilder::addParameter); | ||
|
||
return uriBuilder.toString(); | ||
} | ||
|
||
|
@@ -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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant |
||
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()); | ||
} | ||
AntoxaAntoxic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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())); | ||
AntoxaAntoxic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
private static String encodeTransparencyParams(List<Integer> dsaParams) { | ||
return dsaParams.stream().map(Objects::toString).collect(Collectors.joining( | ||
TRANSPARENCY_TEMPLATE_PARAMS_DELIMITER)); | ||
} | ||
Comment on lines
+285
to
+306
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are some differences with GO logic:
Similar problems with
And so on :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To me the Java implementation looks correct, and the GO version incorrect. |
||
|
||
private static MultiMap resolveHeaders(Site site, Device device, User user) { | ||
final MultiMap headers = MultiMap.caseInsensitiveMultiMap() | ||
.add(HttpUtil.ACCEPT_HEADER, HttpHeaderValues.APPLICATION_JSON); | ||
|
@@ -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; | ||
} | ||
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe this error should be returned in the response, not just logged There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would require a big refactoring of the existing implementation as there is no support for returning valid bids along with failed bids. Yieldlab did not write the existing implementation, and we feel it is beyond the scope of this ticket to be refactoring the existing implementation. Our goal is simply to add DSA support since this is a legal requirement. We are fine with not reporting failures. |
||
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 |
---|---|---|
|
@@ -22,4 +22,6 @@ public class YieldlabResponse { | |
Integer did; | ||
|
||
String pvid; | ||
|
||
YieldlabDigitalServicesActResponse dsa; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comparing the logic with the GO version, I don't see any sorting operations for adSlots in GO. Does PBS-Java need this?
adSlotIds.stream().sorted().collect(...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It isn't needed, no.