Skip to content

Yahoo Ads: Use bid.mtype and fix bid type on multi-format impressions - #4669

Open
MananRPatel wants to merge 10 commits into
prebid:masterfrom
MananRPatel:yahooAdsMtype
Open

MananRPatel wants to merge 10 commits into
prebid:masterfrom
MananRPatel:yahooAdsMtype

Conversation

@MananRPatel

@MananRPatel MananRPatel commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

🔧 Type of changes

  • new bid adapter
  • bid adapter update
  • new feature
  • new analytics adapter
  • new module
  • module update
  • bugfix
  • documentation
  • configuration
  • dependency update
  • tech debt (test coverage, refactorings, etc.)

✨ What's the context?

This PR does two things in the Yahoo Ads bidder. The same code serves the yssp, yahoossp and yahooAdvertising aliases.

Use bid.mtype: Yahoo Ads SSP is adding bid.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:

  1. mtype: 1 is banner, 2 is video. Other values are not supported by this bidder, so the bid is skipped with a badServerResponse error (Unsupported mtype <n> for bid '<id>') and the other bids are kept.
  2. No mtype (null or 0) on a banner+video impression: a <VAST element in adm means video, otherwise banner.
  3. No mtype on a single-format impression: the impression's format, as before.

Unchanged: bids with no mtype on impressions with neither banner nor video are skipped, and an unknown impid still fails with Unknown ad unit code. Changed: on a single-format impression, an audio or native mtype now skips the bid.

🧪 Test plan

  • New makeBids cases in YahooAdsBidderTest cover each rule above: mtype 1 and 2 used as is, a zero mtype falling back to the markup, no mtype with VAST or HTML markup on a banner+video impression, <VAST after an XML declaration and with attributes on the next line, the lowercase <vast>, <VASTAdTagURI> and <div>VAST</div> non-matches, a missing adm, and a banner-only impression with HTML markup and no mtype staying banner.
  • One case sends mtype 3, 4 and 2 on the same impression: the first two are reported as badServerResponse errors with their error text, and the mtype 2 bid is still returned as video. A banner-only case keeps an mtype 1 bid as banner and skips an mtype 4 bid with the same error.
  • mvn test and checkstyle pass.

🏎 Quality check

  • Are your changes following our code style guidelines?
  • Are there any breaking changes in your code?
  • Does your test coverage exceed 90%?
  • Are there any erroneous console logs, debuggers or leftover code in your changes?

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.

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.
MananRPatel added 3 commits October 18, 2026 18:02
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.
@MananRPatel MananRPatel changed the title Yahoo Ads Adapter: resolve bid type from mtype before the impression Yahoo Ads: Resolve bid media type from mtype before the impression Oct 21, 2026
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.
@MananRPatel
MananRPatel marked this pull request as ready for review October 21, 2026 18:33
@MananRPatel MananRPatel changed the title Yahoo Ads: Resolve bid media type from mtype before the impression Yahoo Ads: Use bid.mtype and fix bid type on multi-format impressions Oct 21, 2026

@antoxas1986 antoxas1986 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, updated the description to say the skip applies only when mtype is not set.

}

@Test
public void makeBidsShouldReturnErrorForUnsupportedMtypeAndKeepOtherBids() throws JsonProcessingException {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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()));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/>]");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, added a comment.

}

private static BidType resolveBidType(Bid bid, Imp imp) {
final Integer mtype = bid.getMtype();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, good idea. Moved it into the switch, using getIfNull since defaultIfNull is deprecated in our commons-lang3 version.

}

@Test
public void makeHttpRequestsShouldNotAddBannerWhenImpHasOnlyVideo() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, removed them so this PR stays on the bid type fix.

MananRPatel added 5 commits October 22, 2026 23:32
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 antoxas1986 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +264 to +266
private static List<BidderBid> extractBids(BidResponse bidResponse,
BidRequest bidRequest,
List<BidderError> errors) {

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.

Add empty line after multiline declaration

Comment on lines +274 to +276
private static List<BidderBid> bidsFromResponse(BidResponse bidResponse,
List<Imp> imps,
List<BidderError> errors) {

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.

same

@Test
public void makeBidsShouldReturnVideoBidWhenMtypeIsVideoOnMultiFormatImp() throws JsonProcessingException {
// given
final Bid bid = Bid.builder().impid("321").mtype(2).adm("<VAST version=\"4.0\"></VAST>").build();

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.

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();

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.

remove adm and remove video & banner objects from imp for this test

Comment on lines +453 to +468
@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"));
}

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.

No need for this test

Comment on lines +585 to +598
@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"));
}

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.

No need for this test

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants