-
Notifications
You must be signed in to change notification settings - Fork 248
Yahoo Ads: Migrate to OpenRTB 2.6 #4521
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
base: master
Are you sure you want to change the base?
Changes from all commits
75b65c6
1e55870
cb2fa2c
c765516
3fc6153
a385e19
7059ff9
68fc1c5
81895da
985da4b
e449b0d
219759f
7c56279
b124a99
ff9cb8d
aa383ce
158a0eb
1edf2eb
6e927d4
b6fc04a
8a48b75
58f9f60
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,8 @@ | ||
| package org.prebid.server.bidder.yahooads; | ||
|
|
||
| import com.fasterxml.jackson.core.type.TypeReference; | ||
| import com.fasterxml.jackson.databind.node.ArrayNode; | ||
| import com.fasterxml.jackson.databind.node.IntNode; | ||
| import com.fasterxml.jackson.databind.JsonNode; | ||
| import com.fasterxml.jackson.databind.node.ObjectNode; | ||
| import com.fasterxml.jackson.databind.node.TextNode; | ||
| import com.iab.openrtb.request.App; | ||
| import com.iab.openrtb.request.Banner; | ||
| import com.iab.openrtb.request.BidRequest; | ||
|
|
@@ -20,8 +18,6 @@ | |
| import io.vertx.core.http.HttpMethod; | ||
| import org.apache.commons.collections4.CollectionUtils; | ||
| import org.apache.commons.lang3.StringUtils; | ||
| import org.prebid.server.auction.versionconverter.BidRequestOrtbVersionConversionManager; | ||
| import org.prebid.server.auction.versionconverter.OrtbVersion; | ||
| import org.prebid.server.bidder.Bidder; | ||
| import org.prebid.server.bidder.model.BidderBid; | ||
| import org.prebid.server.bidder.model.BidderCall; | ||
|
|
@@ -32,9 +28,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.FlexibleExtension; | ||
| 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.yahooads.ExtImpYahooAds; | ||
| import org.prebid.server.proto.openrtb.ext.response.BidType; | ||
| import org.prebid.server.util.HttpUtil; | ||
|
|
@@ -44,24 +38,24 @@ | |
| import java.util.Collections; | ||
| import java.util.List; | ||
| import java.util.Objects; | ||
| import java.util.Optional; | ||
|
|
||
| public class YahooAdsBidder implements Bidder<BidRequest> { | ||
|
|
||
| private static final TypeReference<ExtPrebid<?, ExtImpYahooAds>> YAHOO_ADVERTISING_EXT_TYPE_REFERENCE = | ||
| new TypeReference<>() { | ||
| }; | ||
|
|
||
| private static final String OPENRTB_VERSION = "2.6"; | ||
| private static final String GPP_PROPERTY = "gpp"; | ||
| private static final String GPP_SID_PROPERTY = "gpp_sid"; | ||
|
|
||
| private final String endpointUrl; | ||
| private final BidRequestOrtbVersionConversionManager conversionManager; | ||
| private final JacksonMapper mapper; | ||
|
|
||
| public YahooAdsBidder(String endpointUrl, | ||
| BidRequestOrtbVersionConversionManager conversionManager, | ||
| JacksonMapper mapper) { | ||
| this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl)); | ||
| this.mapper = Objects.requireNonNull(mapper); | ||
| this.conversionManager = Objects.requireNonNull(conversionManager); | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -70,16 +64,14 @@ public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest bidRequ | |
| final List<BidderError> errors = new ArrayList<>(); | ||
|
|
||
| final Regs regs = bidRequest.getRegs(); | ||
| final BidRequest bidRequestOpenRtb25 = this.conversionManager.convertFromAuctionSupportedVersion(bidRequest, | ||
| OrtbVersion.ORTB_2_5); | ||
| final Regs promotedRegs = regs != null ? promoteRegsExtToTopLevel(regs) : null; | ||
|
|
||
| final List<Imp> impList = bidRequestOpenRtb25.getImp(); | ||
| final List<Imp> impList = bidRequest.getImp(); | ||
| for (int i = 0; i < impList.size(); i++) { | ||
| try { | ||
| final Imp imp = impList.get(i); | ||
| final ExtImpYahooAds extImpYahooAds = parseAndValidateImpExt(imp.getExt(), i); | ||
| final BidRequest modifiedRequest = modifyRequest(bidRequestOpenRtb25, imp, extImpYahooAds, | ||
| regs); | ||
| final BidRequest modifiedRequest = modifyRequest(bidRequest, imp, extImpYahooAds, promotedRegs); | ||
| bidRequests.add(makeHttpRequest(modifiedRequest)); | ||
| } catch (PreBidException e) { | ||
| errors.add(BidderError.badInput(e.getMessage())); | ||
|
|
@@ -111,8 +103,7 @@ private ExtImpYahooAds parseAndValidateImpExt(ObjectNode impExtNode, int index) | |
| return extImpYahooAds; | ||
| } | ||
|
|
||
| private BidRequest modifyRequest(BidRequest request, Imp imp, ExtImpYahooAds extImpYahooAds, | ||
| Regs regs) { | ||
| private BidRequest modifyRequest(BidRequest request, Imp imp, ExtImpYahooAds extImpYahooAds, Regs regs) { | ||
| final BidRequest.BidRequestBuilder requestBuilder = request.toBuilder(); | ||
|
|
||
| final Site site = request.getSite(); | ||
|
|
@@ -125,7 +116,7 @@ private BidRequest modifyRequest(BidRequest request, Imp imp, ExtImpYahooAds ext | |
| } | ||
|
|
||
| if (regs != null) { | ||
| requestBuilder.regs(modifyRegs(regs)); | ||
| requestBuilder.regs(regs); | ||
| } | ||
|
|
||
| return requestBuilder | ||
|
|
@@ -170,50 +161,83 @@ private static Banner modifyBanner(Banner banner) { | |
| .build(); | ||
| } | ||
|
|
||
| private Regs modifyRegs(Regs regs) { | ||
| final ExtRegs extRegs = resolveExtRegs(regs); | ||
| private static Regs promoteRegsExtToTopLevel(Regs regs) { | ||
| final ExtRegs ext = regs.getExt(); | ||
| if (ext == null || ext.getProperties().isEmpty()) { | ||
| return regs; | ||
| } | ||
|
|
||
| final String promotedGpp = gppToPromote(regs, ext); | ||
| final List<Integer> promotedGppSid = gppSidToPromote(regs, ext); | ||
|
|
||
| return Regs.builder().ext(extRegs).build(); | ||
| } | ||
| final boolean gppSuperseded = ext.containsProperty(GPP_PROPERTY) | ||
| && (promotedGpp != null || regs.getGpp() != null); | ||
| final boolean gppSidSuperseded = ext.containsProperty(GPP_SID_PROPERTY) | ||
| && (promotedGppSid != null || !CollectionUtils.isEmpty(regs.getGppSid())); | ||
|
|
||
| private ExtRegs resolveExtRegs(Regs regs) { | ||
| final Integer gdpr = resolveGdpr(regs); | ||
| final String usPrivacy = resolveUsPrivacy(regs); | ||
| final String gpp = regs.getGpp(); | ||
| final List<Integer> gppSid = regs.getGppSid(); | ||
|
|
||
| final String gpc = Optional.ofNullable(regs.getExt()) | ||
| .map(ExtRegs::getGpc) | ||
| .orElse(null); | ||
| final ExtRegsDsa dsa = Optional.ofNullable(regs.getExt()) | ||
| .map(ExtRegs::getDsa) | ||
| .orElse(null); | ||
| final ExtRegs extRegs = ExtRegs.of(gdpr, usPrivacy, gpc, dsa); | ||
| extRegs.addProperty("gpp", TextNode.valueOf(gpp)); | ||
| if (!CollectionUtils.isEmpty(gppSid)) { | ||
| final ArrayNode gppArrayNode = mapper.mapper().createArrayNode(); | ||
| gppSid.forEach(gppArrayNode::add); | ||
| extRegs.addProperty("gpp_sid", gppArrayNode); | ||
| if (!gppSuperseded && !gppSidSuperseded) { | ||
| return regs; | ||
| } | ||
| if (regs.getCoppa() != null) { | ||
| extRegs.addProperty("coppa", IntNode.valueOf(regs.getCoppa())); | ||
|
|
||
| final Regs.RegsBuilder builder = regs.toBuilder(); | ||
| if (promotedGpp != null) { | ||
| builder.gpp(promotedGpp); | ||
| } | ||
| if (promotedGppSid != null) { | ||
| builder.gppSid(promotedGppSid); | ||
| } | ||
| return builder | ||
| .ext(removeSupersededKeys(ext, gppSuperseded, gppSidSuperseded)) | ||
| .build(); | ||
| } | ||
|
|
||
| Optional.ofNullable(regs.getExt()) | ||
| .map(FlexibleExtension::getProperties) | ||
| .ifPresent(extRegs::addProperties); | ||
| private static String gppToPromote(Regs regs, ExtRegs ext) { | ||
| if (regs.getGpp() != null) { | ||
| return null; | ||
| } | ||
| final JsonNode node = ext.getProperties().get(GPP_PROPERTY); | ||
| return node != null && node.isTextual() ? node.asText() : null; | ||
| } | ||
|
|
||
| return extRegs; | ||
| private static List<Integer> gppSidToPromote(Regs regs, ExtRegs ext) { | ||
| if (!CollectionUtils.isEmpty(regs.getGppSid())) { | ||
| return null; | ||
| } | ||
| final JsonNode node = ext.getProperties().get(GPP_SID_PROPERTY); | ||
| if (node == null || !node.isArray() || node.isEmpty()) { | ||
| return null; | ||
| } | ||
| final List<Integer> sids = new ArrayList<>(node.size()); | ||
| for (final JsonNode elem : node) { | ||
| if (!elem.isIntegralNumber() || !elem.canConvertToInt()) { | ||
| return null; | ||
| } | ||
| sids.add(elem.asInt()); | ||
| } | ||
| return sids; | ||
|
Comment on lines
+206
to
+217
Collaborator
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.
Author
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. Jackson's coercion is more permissive than we want here. convertValue to List turns [6.5] and ["6"] into [6], and keeps [null] as [null], so a gpp_sid we couldn't parse cleanly would still go out as a valid section id. The current loop rejects the array and leaves it in regs.ext instead. |
||
| } | ||
|
|
||
| private static Integer resolveGdpr(Regs regs) { | ||
| return regs.getGdpr() != null ? regs.getGdpr() | ||
| : (regs.getExt() != null ? regs.getExt().getGdpr() : null); | ||
| private static ExtRegs removeSupersededKeys(ExtRegs ext, | ||
| boolean gppSuperseded, | ||
| boolean gppSidSuperseded) { | ||
| final ExtRegs result = ExtRegs.of( | ||
| ext.getGdpr(), ext.getUsPrivacy(), ext.getGpc(), ext.getDsa()); | ||
| ext.getProperties().forEach((key, value) -> { | ||
| final boolean isSupersededKey = (gppSuperseded && GPP_PROPERTY.equals(key)) | ||
| || (gppSidSuperseded && GPP_SID_PROPERTY.equals(key)); | ||
| if (!isSupersededKey) { | ||
| result.addProperty(key, value); | ||
| } | ||
| }); | ||
| return isExtEmpty(result) ? null : result; | ||
| } | ||
|
|
||
| private static String resolveUsPrivacy(Regs regs) { | ||
| return regs.getUsPrivacy() != null ? regs.getUsPrivacy() | ||
| : (regs.getExt() != null ? regs.getExt().getUsPrivacy() : null); | ||
| private static boolean isExtEmpty(ExtRegs ext) { | ||
| return ext.getGdpr() == null | ||
| && ext.getUsPrivacy() == null | ||
| && ext.getGpc() == null | ||
| && ext.getDsa() == null | ||
| && ext.getProperties().isEmpty(); | ||
| } | ||
|
|
||
| private HttpRequest<BidRequest> makeHttpRequest(BidRequest outgoingRequest) { | ||
|
|
@@ -228,7 +252,7 @@ private HttpRequest<BidRequest> makeHttpRequest(BidRequest outgoingRequest) { | |
|
|
||
| private static MultiMap makeHeaders(Device device) { | ||
| final MultiMap headers = HttpUtil.headers() | ||
| .add(HttpUtil.X_OPENRTB_VERSION_HEADER, "2.5"); | ||
| .add(HttpUtil.X_OPENRTB_VERSION_HEADER, OPENRTB_VERSION); | ||
|
|
||
| final String deviceUa = device != null ? device.getUa() : null; | ||
| HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.USER_AGENT_HEADER, deviceUa); | ||
|
|
||
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.
I'm not sure you need all the
promote*methods, since we already do that for you (seeBidRequestOrtb25To26Converter).Also, is it even possible for
gpp,gppSid, andcoppato be inregs.ext? We don't even read them from there in the privacy services.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.
Thanks for the review @CTMBNara.
I looked into
BidRequestOrtb25To26Converteron this. It promotesgdprandus_privacyup fromregs.ext, but it doesn't touchgpp,gpp_sid, orcoppa, so those three don't get promoted for us anywhere in core. Let me know if I've missed something there.Main reason I kept the promotion was it's a no-op for normal requests (values are already top-level, so it skips), but if any arrive under
regs.extthey'd otherwise stay nested and get dropped on the way out. Since these are GPP/COPPA signals, I'd rather not risk silently losing one during the cutover. It only kicks in when the top-level field is empty, and leaves everything else inregs.extuntouched.