Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
75b65c6
Yahoo Ads: migrate outbound wire from OpenRTB 2.5 to 2.6
May 29, 2026
1e55870
Yahoo Ads: preserve non-promoted regs.ext values during 2.6 promotion
May 29, 2026
cb2fa2c
Yahoo Ads: treat a non-integer gpp_sid element as malformed
Jun 1, 2026
c765516
Merge remote-tracking branch 'upstream/master' into YahooAds26
Jun 1, 2026
3fc6153
Yahoo Ads: make regs ext-to-top promotion consistent and self-describing
Jun 1, 2026
a385e19
Merge remote-tracking branch 'upstream/master' into YahooAds26
Jun 10, 2026
7059ff9
Merge remote-tracking branch 'upstream/master' into YahooAds26
Jul 15, 2026
68fc1c5
Yahoo Ads: strip superseded regs.ext copies so top-level always wins
Jul 16, 2026
81895da
Yahoo Ads: treat out-of-int-range regs.ext values as malformed
Jul 19, 2026
985da4b
Yahoo Ads: name the outbound OpenRTB version constant
Jul 19, 2026
e449b0d
Yahoo Ads: exercise regs.ext promotion in the integration test
Jul 19, 2026
219759f
Yahoo Ads: promote regs.ext once per request instead of per imp
Jul 19, 2026
7c56279
Yahoo Ads: drop coppa from the regs.ext promotion
Jul 20, 2026
b124a99
Yahoo Ads: reject fractional gpp_sid elements and pin promotion edge …
Jul 20, 2026
ff9cb8d
Yahoo Ads: drop stale coppa assertions from the wrong-type test
Jul 21, 2026
aa383ce
Yahoo Ads: remove coppa references from tests and comments
Jul 21, 2026
158a0eb
Yahoo Ads: pin top-level gpp_sid winning over a conflicting ext copy
Jul 21, 2026
1edf2eb
Yahoo Ads: pin empty top-level gpp counting as set
Jul 21, 2026
6e927d4
Yahoo Ads: pin the empty ext gpp_sid array as not promotable
Jul 21, 2026
b6fc04a
Yahoo Ads: pin the shared promoted regs across a multi-imp request
Jul 21, 2026
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
139 changes: 85 additions & 54 deletions src/main/java/org/prebid/server/bidder/yahooads/YahooAdsBidder.java

Copy link
Copy Markdown
Collaborator

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 (see BidRequestOrtb25To26Converter).

Also, is it even possible for gpp, gppSid, and coppa to be in regs.ext? We don't even read them from there in the privacy services.

Copy link
Copy Markdown
Author

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 BidRequestOrtb25To26Converter on this. It promotes gdpr and us_privacy up from regs.ext, but it doesn't touch gpp, gpp_sid, or coppa, 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.ext they'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 in regs.ext untouched.

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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -70,16 +64,15 @@ 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()));
Expand Down Expand Up @@ -112,7 +105,7 @@ private ExtImpYahooAds parseAndValidateImpExt(ObjectNode impExtNode, int index)
}

private BidRequest modifyRequest(BidRequest request, Imp imp, ExtImpYahooAds extImpYahooAds,
Regs regs) {
Regs promotedRegs) {
final BidRequest.BidRequestBuilder requestBuilder = request.toBuilder();

final Site site = request.getSite();
Expand All @@ -124,8 +117,8 @@ private BidRequest modifyRequest(BidRequest request, Imp imp, ExtImpYahooAds ext
requestBuilder.app(app.toBuilder().id(extImpYahooAds.getDcn()).build());
}

if (regs != null) {
requestBuilder.regs(modifyRegs(regs));
if (promotedRegs != null) {
requestBuilder.regs(promotedRegs);
}

return requestBuilder
Expand Down Expand Up @@ -170,50 +163,88 @@ private static Banner modifyBanner(Banner banner) {
.build();
}

private Regs modifyRegs(Regs regs) {
final ExtRegs extRegs = resolveExtRegs(regs);
// Promote legacy 2.5 regs.ext gpp/gpp_sid to their 2.6 top-level slots.
// An ext key is removed whenever the outbound request has a top-level value for that
// field, so one signal never goes out with two values; everything else stays in ext.
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);
// Value to lift from ext, or null when top-level already has it or ext lacks a valid value.
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;
}

private static Integer resolveGdpr(Regs regs) {
return regs.getGdpr() != null ? regs.getGdpr()
: (regs.getExt() != null ? regs.getExt().getGdpr() : null);
// Rebuild regs.ext keeping the typed fields and every property except the superseded keys.
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) {
Expand All @@ -228,7 +259,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);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.prebid.server.spring.config.bidder;

import org.prebid.server.auction.versionconverter.BidRequestOrtbVersionConversionManager;
import org.prebid.server.bidder.BidderDeps;
import org.prebid.server.bidder.yahooads.YahooAdsBidder;
import org.prebid.server.json.JacksonMapper;
Expand All @@ -26,12 +25,11 @@ BidderConfigurationProperties configurationProperties() {

@Bean
BidderDeps yahooAdsBidderDeps(BidderConfigurationProperties yahooAdsConfigurationProperties,
JacksonMapper mapper,
BidRequestOrtbVersionConversionManager conversionManager) {
JacksonMapper mapper) {

return BidderDepsAssembler.forBidder(BIDDER_NAME)
.withConfig(yahooAdsConfigurationProperties)
.bidderCreator(config -> new YahooAdsBidder(config.getEndpoint(), conversionManager, mapper))
.bidderCreator(config -> new YahooAdsBidder(config.getEndpoint(), mapper))
.assemble();
}
}
Loading