Repository navigation
Yahoo Ads: Use bid.mtype and fix bid type on multi-format impressions - #4669
MananRPatel wants to merge 10 commits into
Conversation
On a banner+video impression the bidder labeled every bid as banner because it only looked at the impression. Read bid.mtype first, fall back to a VAST root tag check on adm when mtype is absent, and use the impression only when it offers a single format.
A bid with an mtype other than banner or video is now skipped with a badServerResponse error instead of being dropped silently; other bids in the response are kept. Tests are consolidated with shared helpers.
The VAST check finds a VAST element anywhere in adm, not only at the root, so drop "Root" from the test name.
XML names are case-sensitive and the VAST root element is always VAST, so drop the case-insensitive flag and the optional whitespace after "<". Align the markup tests: VAST after an XML declaration and with attributes on the next line is video; lowercase vast and a bare VASTAdTagURI are banner.
antoxas1986
left a comment
There was a problem hiding this comment.
Nice fix. resolveBidType follows the same order as the Go change in prebid/prebid-server#5032 (mtype, then <VAST on banner+video impressions, then the impression's format), so the two servers will agree on bid types. Treating a null and a 0 mtype the same is correct. Nothing blocking from me, just a few small comments and nits inline.
|
|
||
| private static BidType resolveBidType(Bid bid, Imp imp) { | ||
| final Integer mtype = bid.getMtype(); | ||
| if (mtype != null && mtype != 0) { |
There was a problem hiding this comment.
Same point as on the Go PR. The description says "bids on impressions with neither banner nor video are skipped", but a non-zero mtype is used before the impression's formats are looked at. mtype: 1 on a video-only impression, or on an impression with neither format, still gets a type. This is probably unreachable in practice, since the core removes formats the bidder doesn't support. Could you either update the description or add a test that pins this behaviour?
There was a problem hiding this comment.
Thanks, updated the description to say the skip applies only when mtype is not set.
| } | ||
|
|
||
| @Test | ||
| public void makeBidsShouldReturnErrorForUnsupportedMtypeAndKeepOtherBids() throws JsonProcessingException { |
There was a problem hiding this comment.
The description lists "on a single-format impression, an audio or native mtype now skips the bid" as a behaviour change, but the tests only check unsupported mtype on givenMultiFormatImp(). Could you add a makeBids case with a banner-only impression and mtype(4)? That would match mtype native on banner-only imp is dropped in the Go PR. A case with mtype(2) on a banner-only impression (used as is) would also match the Go tests.
There was a problem hiding this comment.
Thanks, added a banner-only test: mtype 1 is kept as banner and mtype 4 is dropped. Go now checks the same pair, so both PRs match.
| return imp; | ||
| } | ||
| } | ||
| throw new PreBidException("Unknown ad unit code '%s'".formatted(bid.getImpid())); |
There was a problem hiding this comment.
This already happened before this PR: an unknown impid makes the exception escape the stream, and makeBids returns Result.withError(...). That drops valid bids and any errors already collected. Now that errors are collected per bid, consider adding this error and continuing too. It's a behaviour change, so it's fine as a follow-up (same as on the Go PR).
There was a problem hiding this comment.
Agreed, will handle this in a separate PR.
| private static final String GPP_PROPERTY = "gpp"; | ||
| private static final String GPP_SID_PROPERTY = "gpp_sid"; | ||
|
|
||
| private static final Pattern VAST_MARKUP = Pattern.compile("<VAST[ \\t\\r\\n/>]"); |
There was a problem hiding this comment.
Minor: find() looks anywhere in adm, so an HTML banner whose script embeds a VAST string (some outstream renderers do this) would be typed as video on a multi-format impression that has no mtype. That's an acceptable trade-off for the fallback path. A short comment saying it's a heuristic for responses without mtype would help the next reader.
There was a problem hiding this comment.
Thanks, added a comment.
| } | ||
|
|
||
| private static BidType resolveBidType(Bid bid, Imp imp) { | ||
| final Integer mtype = bid.getMtype(); |
There was a problem hiding this comment.
nit (optional): ObjectUtils.defaultIfNull(bid.getMtype(), 0) with a case 0 -> branch in the switch would put all of the mtype handling in one place. Just a matter of taste.
There was a problem hiding this comment.
Thanks, good idea. Moved it into the switch, using getIfNull since defaultIfNull is deprecated in our commons-lang3 version.
| } | ||
|
|
||
| @Test | ||
| public void makeHttpRequestsShouldNotAddBannerWhenImpHasOnlyVideo() { |
There was a problem hiding this comment.
nit: these request-side tests (video-only impression, banner with only w or only h, and the regs.ext fields kept after the gpp promotion) are useful, but they don't relate to the bid-type fix. Just a heads-up in case the maintainers would rather have them in a separate PR.
There was a problem hiding this comment.
Thanks, removed them so this PR stays on the bid type fix.
Cover mtype on a single-format impression: on a banner-only impression an mtype 1 bid is kept as banner and an mtype 4 bid is skipped with the unsupported mtype error. Use HTML markup in the banner-only test without mtype, since VAST markup on a banner-only impression is not a real case.
antoxas1986
left a comment
There was a problem hiding this comment.
Thanks for the quick follow-ups. All comments are addressed: the description is updated, there's a banner-only mtype test that matches the Go PR, the VAST heuristic has a comment, mtype is handled in one switch via getIfNull, and the unrelated request-side tests are gone. The unknown-impid handling is agreed as a follow-up PR. LGTM. Note: the CI workflows on the latest commit are still waiting for a maintainer to approve them, so tests haven't run yet.
| private static List<BidderBid> extractBids(BidResponse bidResponse, | ||
| BidRequest bidRequest, | ||
| List<BidderError> errors) { |
There was a problem hiding this comment.
Add empty line after multiline declaration
| private static List<BidderBid> bidsFromResponse(BidResponse bidResponse, | ||
| List<Imp> imps, | ||
| List<BidderError> errors) { |
| @Test | ||
| public void makeBidsShouldReturnVideoBidWhenMtypeIsVideoOnMultiFormatImp() throws JsonProcessingException { | ||
| // given | ||
| final Bid bid = Bid.builder().impid("321").mtype(2).adm("<VAST version=\"4.0\"></VAST>").build(); |
There was a problem hiding this comment.
remove adm and remove video & banner objects from imp for this test
| @Test | ||
| public void makeBidsShouldReturnBannerBidWhenMtypeIsBannerOnMultiFormatImp() throws JsonProcessingException { | ||
| // given | ||
| final Bid bid = Bid.builder().impid("321").mtype(1).adm("<div>ad</div>").build(); |
There was a problem hiding this comment.
remove adm and remove video & banner objects from imp for this test
| @Test | ||
| public void makeBidsShouldReturnErrorForUnsupportedMtypeOnBannerOnlyImp() throws JsonProcessingException { | ||
| // given | ||
| final Bid bannerBid = Bid.builder().id("bannerBidId").impid("321").mtype(1).adm("<div>ad</div>").build(); | ||
| final Bid nativeBid = Bid.builder().id("nativeBidId").impid("321").mtype(4).build(); | ||
| final Imp bannerOnlyImp = Imp.builder().id("321").banner(Banner.builder().build()).build(); | ||
| final BidderCall<BidRequest> httpCall = givenHttpCall(bannerOnlyImp, bannerBid, nativeBid); | ||
|
|
||
| // when | ||
| final Result<List<BidderBid>> result = target.makeBids(httpCall, null); | ||
|
|
||
| // then | ||
| assertThat(result.getErrors()) | ||
| .containsExactly(BidderError.badServerResponse("Unsupported mtype 4 for bid 'nativeBidId'")); | ||
| assertThat(result.getValue()).containsExactly(BidderBid.of(bannerBid, banner, "USD")); | ||
| } |
| @Test | ||
| public void makeBidsShouldReturnBannerBidWhenNoMtypeOnBannerOnlyImp() throws JsonProcessingException { | ||
| // given | ||
| final Bid bid = Bid.builder().impid("321").adm("<div>ad</div>").build(); | ||
| final Imp bannerOnlyImp = Imp.builder().id("321").banner(Banner.builder().build()).build(); | ||
| final BidderCall<BidRequest> httpCall = givenHttpCall(bannerOnlyImp, bid); | ||
|
|
||
| // when | ||
| final Result<List<BidderBid>> result = target.makeBids(httpCall, null); | ||
|
|
||
| // then | ||
| assertThat(result.getErrors()).isEmpty(); | ||
| assertThat(result.getValue()).containsOnly(BidderBid.of(bid, banner, "USD")); | ||
| } |
🔧 Type of changes
✨ What's the context?
This PR does two things in the Yahoo Ads bidder. The same code serves the
yssp,yahoosspandyahooAdvertisingaliases.Use
bid.mtype: Yahoo Ads SSP is addingbid.mtype(OpenRTB 2.6) to its responses. The bidder now reads it to set each bid's media type.Fix multi-format bids: today the bidder takes the type from the impression only and checks banner first. On an impression that offers both banner and video, every bid, including a VAST bid, is returned as
banner. Reading the type from the bid fixes this.🧠 Rationale behind the change
The bid type is now taken from the bid, in this order:
mtype: 1 is banner, 2 is video. Other values are not supported by this bidder, so the bid is skipped with abadServerResponseerror (Unsupported mtype <n> for bid '<id>') and the other bids are kept.mtype(null or 0) on a banner+video impression: a<VASTelement inadmmeans video, otherwise banner.mtypeon a single-format impression: the impression's format, as before.Unchanged: bids with no
mtypeon impressions with neither banner nor video are skipped, and an unknownimpidstill fails withUnknown ad unit code. Changed: on a single-format impression, an audio or nativemtypenow skips the bid.🧪 Test plan
makeBidscases inYahooAdsBidderTestcover each rule above:mtype1 and 2 used as is, a zeromtypefalling back to the markup, nomtypewith VAST or HTML markup on a banner+video impression,<VASTafter an XML declaration and with attributes on the next line, the lowercase<vast>,<VASTAdTagURI>and<div>VAST</div>non-matches, a missingadm, and a banner-only impression with HTML markup and nomtypestaying banner.mtype3, 4 and 2 on the same impression: the first two are reported asbadServerResponseerrors with their error text, and themtype2 bid is still returned as video. A banner-only case keeps anmtype1 bid as banner and skips anmtype4 bid with the same error.mvn testand checkstyle pass.🏎 Quality check
Companion change: prebid/prebid-server#5032 applies the same order to PBS-Go.
For contact: prebid-tech-team@yahooinc.com
Implemented with assistance from Claude Code.