Skip to content
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

Core: Add validation by BIDDER.yaml for media types #1798

Merged
merged 24 commits into from
Jun 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
64e5904
Add bid request processor which filter imps by bidder supported media…
CTMBNara Mar 30, 2022
7253793
Fix checkstyle.
CTMBNara Mar 31, 2022
f594b0d
Add unit test for ExchangeService.
CTMBNara Mar 31, 2022
d7491e7
Add unit tests for BidderMediaTypeProcessor.
CTMBNara Mar 31, 2022
217feba
Change error message.
CTMBNara Apr 4, 2022
84ba175
Resolve conversations.
CTMBNara Apr 5, 2022
7f6fe8c
Refactor.
CTMBNara Apr 5, 2022
0c3601e
Merge remote-tracking branch 'origin/master' into add-filter-for-imp-…
CTMBNara Apr 12, 2022
7be8653
Move messages to warnings.
CTMBNara Apr 12, 2022
c337b85
Fix bugs related to `BidderSeatBid.warnings` field.
CTMBNara Apr 27, 2022
31799fb
Merge remote-tracking branch 'origin/master' into add-filter-for-imp-…
CTMBNara May 11, 2022
214725e
Merge remote-tracking branch 'origin/master' into add-filter-for-imp-…
CTMBNara May 19, 2022
dae9ab9
Resolve merge conflicts.
CTMBNara May 19, 2022
af7585d
Validation by BIDDER.yaml for media types tests (#1606)
mtuchkova May 25, 2022
ff654db
Merge remote-tracking branch 'origin/master' into add-filter-for-imp-…
CTMBNara May 26, 2022
14801f1
Resolve merge conflicts.
CTMBNara May 26, 2022
7e24a96
Merge remote-tracking branch 'origin/master' into add-filter-for-imp-…
CTMBNara Jun 6, 2022
1c0c7e3
Merge branch 'master' into add-filter-for-imp-media-type
Jun 10, 2022
6215b68
Merge remote-tracking branch 'origin/master' into add-filter-for-imp-…
CTMBNara Jun 17, 2022
7833c98
Resolve merge conflicts.
CTMBNara Jun 17, 2022
688155e
Resolve merge conflicts.
CTMBNara Jun 18, 2022
1dc3bbb
Merge branch 'master' into add-filter-for-imp-media-type
SerhiiNahornyi Jun 27, 2022
21518a4
Resolve conversations.
CTMBNara Jun 27, 2022
5d18da6
Merge remote-tracking branch 'origin/master' into add-filter-for-imp-…
CTMBNara Jun 27, 2022
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
36 changes: 32 additions & 4 deletions src/main/java/org/prebid/server/auction/ExchangeService.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.prebid.server.auction.adjustment.BidAdjustmentFactorResolver;
import org.prebid.server.auction.mediatypeprocessor.MediaTypeProcessingResult;
import org.prebid.server.auction.mediatypeprocessor.MediaTypeProcessor;
import org.prebid.server.auction.model.AuctionContext;
import org.prebid.server.auction.model.AuctionParticipation;
import org.prebid.server.auction.model.BidRequestCacheInfo;
Expand Down Expand Up @@ -159,6 +162,7 @@ public class ExchangeService {
private final FpdResolver fpdResolver;
private final SchainResolver schainResolver;
private final DebugResolver debugResolver;
private final MediaTypeProcessor mediaTypeProcessor;
private final HttpBidderRequester httpBidderRequester;
private final ResponseBidValidator responseBidValidator;
private final CurrencyConversionService currencyService;
Expand All @@ -183,6 +187,7 @@ public ExchangeService(long expectedCacheTime,
FpdResolver fpdResolver,
SchainResolver schainResolver,
DebugResolver debugResolver,
MediaTypeProcessor mediaTypeProcessor,
HttpBidderRequester httpBidderRequester,
ResponseBidValidator responseBidValidator,
CurrencyConversionService currencyService,
Expand Down Expand Up @@ -210,6 +215,7 @@ public ExchangeService(long expectedCacheTime,
this.fpdResolver = Objects.requireNonNull(fpdResolver);
this.schainResolver = Objects.requireNonNull(schainResolver);
this.debugResolver = Objects.requireNonNull(debugResolver);
this.mediaTypeProcessor = Objects.requireNonNull(mediaTypeProcessor);
this.httpBidderRequester = Objects.requireNonNull(httpBidderRequester);
this.responseBidValidator = Objects.requireNonNull(responseBidValidator);
this.currencyService = Objects.requireNonNull(currencyService);
Expand Down Expand Up @@ -1244,7 +1250,28 @@ private Future<BidderResponse> requestBids(BidderRequest bidderRequest,

final long startTime = clock.millis();

return httpBidderRequester.requestBids(bidder, bidderRequest, timeout, requestHeaders, debugEnabledForBidder)
final MediaTypeProcessingResult mediaTypeProcessingResult =
mediaTypeProcessor.process(bidderRequest.getBidRequest(), resolvedBidderName);

if (mediaTypeProcessingResult.isRejected()) {
final BidderSeatBid bidderSeatBid = BidderSeatBid.of(
Collections.emptyList(),
Collections.emptyList(),
Collections.emptyList(),
mediaTypeProcessingResult.getErrors());

return Future.succeededFuture(BidderResponse.of(bidderName, bidderSeatBid, 0));
}

final BidderRequest modifiedBidderRequest = bidderRequest.with(mediaTypeProcessingResult.getBidRequest());

return httpBidderRequester
.requestBids(bidder, modifiedBidderRequest, timeout, requestHeaders, debugEnabledForBidder)
.map(seatBid -> BidderSeatBid.of(
seatBid.getBids(),
seatBid.getHttpCalls(),
seatBid.getErrors(),
ListUtils.union(mediaTypeProcessingResult.getErrors(), seatBid.getWarnings())))
.map(seatBid -> BidderResponse.of(bidderName, seatBid, responseTime(startTime)));
}

Expand Down Expand Up @@ -1328,6 +1355,7 @@ private AuctionParticipation validBidderResponse(AuctionParticipation auctionPar
final BidderResponse bidderResponse = auctionParticipation.getBidderResponse();
final BidderSeatBid seatBid = bidderResponse.getSeatBid();
final List<BidderError> errors = new ArrayList<>(seatBid.getErrors());
final List<BidderError> warnings = new ArrayList<>(seatBid.getWarnings());

final List<String> requestCurrencies = bidRequest.getCur();
if (requestCurrencies.size() > 1) {
Expand Down Expand Up @@ -1365,7 +1393,7 @@ private AuctionParticipation validBidderResponse(AuctionParticipation auctionPar

final BidderResponse resultBidderResponse = errors.isEmpty()
? bidderResponse
: bidderResponse.with(BidderSeatBid.of(validBids, seatBid.getHttpCalls(), errors));
: bidderResponse.with(BidderSeatBid.of(validBids, seatBid.getHttpCalls(), errors, warnings));
return auctionParticipation.with(resultBidderResponse);
}

Expand Down Expand Up @@ -1428,8 +1456,8 @@ private AuctionParticipation applyBidPriceChanges(AuctionParticipation auctionPa
}
}

final BidderResponse resultBidderResponse =
bidderResponse.with(BidderSeatBid.of(updatedBidderBids, seatBid.getHttpCalls(), errors));
final BidderResponse resultBidderResponse = bidderResponse.with(BidderSeatBid.of(
updatedBidderBids, seatBid.getHttpCalls(), errors, seatBid.getWarnings()));
return auctionParticipation.with(resultBidderResponse);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,9 +293,9 @@ private BidderSeatBid makeBidderSeatBid(BidderSeatBid bidderSeatBid, SeatBid sea
if (nonNullBidderSeatBid) {
bidderBids.addAll(bidderSeatBid.getBids());
}
return BidderSeatBid.of(bidderBids,
nonNullBidderSeatBid ? bidderSeatBid.getHttpCalls() : Collections.emptyList(),
nonNullBidderSeatBid ? bidderSeatBid.getErrors() : Collections.emptyList());
return nonNullBidderSeatBid
? bidderSeatBid.with(bidderBids)
: BidderSeatBid.of(bidderBids);
}

private BidderBid makeBidderBid(Bid bid, String bidCurrency, Map<String, BidType> impIdToBidType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -655,14 +655,14 @@ private static List<BidderResponse> removeRejectedBids(List<BidderResponse> bidd
*/
private static BidderResponse removeRejectedBids(BidderResponse bidderResponse, List<String> rejectedBidIds) {
final String bidder = bidderResponse.getBidder();
final BidderSeatBid bidderSeatBid = bidderResponse.getSeatBid();

final List<BidderBid> survivedBidderBids = bidderResponse.getSeatBid().getBids().stream()
final List<BidderBid> survivedBidderBids = bidderSeatBid.getBids().stream()
.filter(bidderBid -> !rejectedBidIds.contains(bidderBid.getBid().getId()))
.collect(Collectors.toList());

final BidderSeatBid bidderSeatBid = bidderResponse.getSeatBid();
return BidderResponse.of(bidder,
BidderSeatBid.of(survivedBidderBids, bidderSeatBid.getHttpCalls(), bidderSeatBid.getErrors()),
bidderSeatBid.with(survivedBidderBids),
bidderResponse.getResponseTime());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package org.prebid.server.auction.mediatypeprocessor;

import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Imp;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.SetUtils;
import org.prebid.server.bidder.BidderCatalog;
import org.prebid.server.bidder.BidderInfo;
import org.prebid.server.bidder.model.BidderError;
import org.prebid.server.spring.config.bidder.model.MediaType;
import org.prebid.server.util.ObjectUtil;

import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* {@link BidderMediaTypeProcessor} is an implementation of {@link MediaTypeProcessor} that
* can be used to remove media types from {@link Imp} unsupported by specific bidder.
*/
public class BidderMediaTypeProcessor implements MediaTypeProcessor {

private final BidderCatalog bidderCatalog;

public BidderMediaTypeProcessor(BidderCatalog bidderCatalog) {
this.bidderCatalog = Objects.requireNonNull(bidderCatalog);
}

@Override
public MediaTypeProcessingResult process(BidRequest bidRequest, String supportedBidderName) {
CTMBNara marked this conversation as resolved.
Show resolved Hide resolved
final Set<MediaType> supportedMediaTypes = extractSupportedMediaTypes(bidRequest, supportedBidderName);
if (supportedMediaTypes.isEmpty()) {
return MediaTypeProcessingResult.rejected(Collections.singletonList(
BidderError.badInput("Bidder does not support any media types.")));
}

final List<BidderError> errors = new ArrayList<>();
final BidRequest modifiedBidRequest = processBidRequest(bidRequest, supportedMediaTypes, errors);

return modifiedBidRequest != null
? MediaTypeProcessingResult.succeeded(modifiedBidRequest, errors)
: MediaTypeProcessingResult.rejected(errors);
}

private Set<MediaType> extractSupportedMediaTypes(BidRequest bidRequest, String supportedBidderName) {
final BidderInfo.CapabilitiesInfo capabilitiesInfo = bidderCatalog
.bidderInfoByName(supportedBidderName).getCapabilities();

final List<MediaType> supportedMediaTypes;
if (bidRequest.getSite() != null) {
supportedMediaTypes = ObjectUtil.getIfNotNull(
capabilitiesInfo.getSite(), BidderInfo.PlatformInfo::getMediaTypes);
} else {
supportedMediaTypes = ObjectUtil.getIfNotNull(
capabilitiesInfo.getApp(), BidderInfo.PlatformInfo::getMediaTypes);
}

return CollectionUtils.isNotEmpty(supportedMediaTypes)
? EnumSet.copyOf(supportedMediaTypes)
: EnumSet.noneOf(MediaType.class);
}

private BidRequest processBidRequest(BidRequest bidRequest,
Set<MediaType> supportedMediaTypes,
List<BidderError> errors) {

final List<Imp> modifiedImps = bidRequest.getImp().stream()
.map(imp -> processImp(imp, supportedMediaTypes, errors))
.filter(Objects::nonNull)
.collect(Collectors.toList());

if (modifiedImps.isEmpty()) {
errors.add(BidderError.badInput("Bid request contains 0 impressions after filtering."));
return null;
}

return bidRequest.toBuilder().imp(modifiedImps).build();
}

private static Imp processImp(Imp imp, Set<MediaType> supportedMediaTypes, List<BidderError> errors) {
final Set<MediaType> impMediaTypes = getMediaTypes(imp);
final Set<MediaType> unsupportedMediaTypes = SetUtils.difference(impMediaTypes, supportedMediaTypes);

if (unsupportedMediaTypes.isEmpty()) {
return imp;
}

if (impMediaTypes.equals(unsupportedMediaTypes)) {
errors.add(BidderError.badInput("Imp " + imp.getId() + " does not have a supported media type "
+ "and has been removed from the request for this bidder."));

return null;
}

final Imp.ImpBuilder impBuilder = imp.toBuilder();
unsupportedMediaTypes.forEach(unsupportedMediaType -> removeMediaType(impBuilder, unsupportedMediaType));

return impBuilder.build();
}

private static Set<MediaType> getMediaTypes(Imp imp) {
return Stream.of(
imp.getBanner() != null ? MediaType.BANNER : null,
imp.getVideo() != null ? MediaType.VIDEO : null,
imp.getAudio() != null ? MediaType.AUDIO : null,
imp.getXNative() != null ? MediaType.NATIVE : null)
.filter(Objects::nonNull)
.collect(Collectors.toCollection(() -> EnumSet.noneOf(MediaType.class)));
}

private static void removeMediaType(Imp.ImpBuilder impBuilder, MediaType mediaType) {
switch (mediaType) {
case BANNER:
impBuilder.banner(null);
break;
case VIDEO:
impBuilder.video(null);
break;
case AUDIO:
impBuilder.audio(null);
break;
case NATIVE:
impBuilder.xNative(null);
break;
default:
throw new IllegalArgumentException("Invalid media type");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.prebid.server.auction.mediatypeprocessor;

import com.iab.openrtb.request.BidRequest;
import lombok.Value;
import org.prebid.server.bidder.model.BidderError;

import java.util.List;

@Value(staticConstructor = "of")
public class MediaTypeProcessingResult {

BidRequest bidRequest;

List<BidderError> errors;

boolean rejected;

public static MediaTypeProcessingResult succeeded(BidRequest bidRequest, List<BidderError> errors) {
return MediaTypeProcessingResult.of(bidRequest, errors, false);
}

public static MediaTypeProcessingResult rejected(List<BidderError> errors) {
return MediaTypeProcessingResult.of(null, errors, true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.prebid.server.auction.mediatypeprocessor;

import com.iab.openrtb.request.BidRequest;

public interface MediaTypeProcessor {

MediaTypeProcessingResult process(BidRequest bidRequest, String supportedBidderName);
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be awesome to add some documentation what the use cases are for this processor is

Copy link
Collaborator

Choose a reason for hiding this comment

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

Done

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.prebid.server.auction.mediatypeprocessor;

import com.iab.openrtb.request.BidRequest;

import java.util.Collections;

public class NoOpMediaTypeProcessor implements MediaTypeProcessor {

@Override
public MediaTypeProcessingResult process(BidRequest bidRequest, String supportedBidderName) {
return MediaTypeProcessingResult.succeeded(bidRequest, Collections.emptyList());
}
}
11 changes: 6 additions & 5 deletions src/main/java/org/prebid/server/bidder/BidderInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import lombok.Value;
import org.apache.commons.lang3.StringUtils;
import org.prebid.server.spring.config.bidder.model.CompressionType;
import org.prebid.server.spring.config.bidder.model.MediaType;

import java.util.List;

Expand Down Expand Up @@ -37,8 +38,8 @@ public static BidderInfo create(boolean enabled,
String endpoint,
String aliasOf,
String maintainerEmail,
List<String> appMediaTypes,
List<String> siteMediaTypes,
List<MediaType> appMediaTypes,
Copy link
Contributor

Choose a reason for hiding this comment

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

I really appreciate string being replaced by proper types 👍

List<MediaType> siteMediaTypes,
List<String> supportedVendors,
int vendorId,
boolean ccpaEnforced,
Expand All @@ -59,7 +60,7 @@ public static BidderInfo create(boolean enabled,
compressionType);
}

private static PlatformInfo platformInfo(List<String> mediaTypes) {
private static PlatformInfo platformInfo(List<MediaType> mediaTypes) {
return mediaTypes != null ? new PlatformInfo(mediaTypes) : null;
}

Expand All @@ -78,10 +79,10 @@ public static class CapabilitiesInfo {
}

@Value
private static class PlatformInfo {
public static class PlatformInfo {

@JsonProperty("mediaTypes")
List<String> mediaTypes;
List<MediaType> mediaTypes;
}

@Value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
import org.prebid.server.auction.ExchangeService;
import org.prebid.server.auction.model.BidderRequest;
import org.prebid.server.bidder.model.BidderBid;
import org.prebid.server.bidder.model.BidderCall;
import org.prebid.server.bidder.model.BidderCallType;
import org.prebid.server.bidder.model.BidderError;
import org.prebid.server.bidder.model.BidderCall;
import org.prebid.server.bidder.model.BidderSeatBid;
import org.prebid.server.bidder.model.HttpRequest;
import org.prebid.server.bidder.model.HttpResponse;
Expand Down Expand Up @@ -164,7 +164,9 @@ private Future<BidderSeatBid> emptyBidderSeatBidWithErrors(List<BidderError> bid
? Collections.singletonList(BidderError.failedToRequestBids(
"The bidder failed to generate any bid requests, but also failed to generate an error"))
: bidderErrors;
return Future.succeededFuture(BidderSeatBid.of(Collections.emptyList(), Collections.emptyList(), errors));

return Future.succeededFuture(BidderSeatBid.of(
Collections.emptyList(), Collections.emptyList(), errors, Collections.emptyList()));
}

/**
Expand Down Expand Up @@ -360,7 +362,7 @@ BidderSeatBid toBidderSeatBid(boolean debugEnabled) {
: Collections.emptyList();

final List<BidderError> errors = combineErrors(previousErrors, httpCalls, errorsRecorded);
return BidderSeatBid.of(bidsRecorded, extHttpCalls, errors);
return BidderSeatBid.of(bidsRecorded, extHttpCalls, errors, Collections.emptyList());
}

/**
Expand Down
Loading