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

Filter Multi-Format Requests #4162

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions adapters/bidder.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ type ExtraRequestInfo struct {
PbsEntryPoint metrics.RequestType
GlobalPrivacyControlHeader string
CurrencyConversions currency.Conversions
PreferredMediaType openrtb_ext.BidType
}

func NewExtraRequestInfo(c currency.Conversions) ExtraRequestInfo {
Expand Down
196 changes: 150 additions & 46 deletions adapters/infoawarebidder.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,55 +54,75 @@ func (i *InfoAwareBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *Ex
allowedMediaTypes = i.info.dooh
}

// Filtering imps is quite expensive (array filter with large, non-pointer elements)... but should be rare,
// because it only happens if the publisher makes a really bad request.
//
// To avoid allocating new arrays and copying in the normal case, we'll make one pass to
// see if any imps need to be removed, and another to do the removing if necessary.
numToFilter, errs := pruneImps(request.Imp, allowedMediaTypes)

// If all imps in bid request come with unsupported media types, exit
if numToFilter == len(request.Imp) {
return nil, append(errs, &errortypes.Warning{Message: "Bid request didn't contain media types supported by the bidder"})
updated, updatedImps, errs := pruneImps(request.Imp, allowedMediaTypes, i.info.multiformat, reqInfo.PreferredMediaType)
if updated {
request.Imp = updatedImps
}

if numToFilter != 0 {
// Filter out imps with unsupported media types
filteredImps, newErrs := filterImps(request.Imp, numToFilter)
request.Imp = filteredImps
errs = append(errs, newErrs...)
// If all imps in bid request are invalid, exit
if len(request.Imp) == 0 {
return nil, append(errs, &errortypes.Warning{Message: "Bid request didn't contain media types supported by the bidder"})
}

reqs, delegateErrs := i.Bidder.MakeRequests(request, reqInfo)
return reqs, append(errs, delegateErrs...)
}

// pruneImps trims invalid media types from each imp, and returns true if any of the
// Imps have _no_ valid Media Types left.
func pruneImps(imps []openrtb2.Imp, allowedTypes parsedSupports) (int, []error) {
numToFilter := 0
// pruneImps trims imps that don't match the allowed types and removes imps that don't have the allowed types.
// It also handles multi-format restrictions if the bidder doesn't support multi-format impressions.
func pruneImps(imps []openrtb2.Imp, allowedTypes parsedSupports, multiformatSupport bool, preferredMediaType openrtb_ext.BidType) (bool, []openrtb2.Imp, []error) {
var updated bool
var errs []error
writeIndex := 0

for i := 0; i < len(imps); i++ {
if !allowedTypes.banner && imps[i].Banner != nil {
imps[i].Banner = nil
imp := &imps[i] // Work with pointer to avoid copying

// Prune unsupported media types
if !allowedTypes.banner && imp.Banner != nil {
imp.Banner = nil
errs = append(errs, &errortypes.Warning{Message: fmt.Sprintf("request.imp[%d] uses banner, but this bidder doesn't support it", i)})
}
if !allowedTypes.video && imps[i].Video != nil {
imps[i].Video = nil
if !allowedTypes.video && imp.Video != nil {
imp.Video = nil
errs = append(errs, &errortypes.Warning{Message: fmt.Sprintf("request.imp[%d] uses video, but this bidder doesn't support it", i)})
}
if !allowedTypes.audio && imps[i].Audio != nil {
imps[i].Audio = nil
if !allowedTypes.audio && imp.Audio != nil {
imp.Audio = nil
errs = append(errs, &errortypes.Warning{Message: fmt.Sprintf("request.imp[%d] uses audio, but this bidder doesn't support it", i)})
}
if !allowedTypes.native && imps[i].Native != nil {
imps[i].Native = nil
if !allowedTypes.native && imp.Native != nil {
imp.Native = nil
errs = append(errs, &errortypes.Warning{Message: fmt.Sprintf("request.imp[%d] uses native, but this bidder doesn't support it", i)})
}
if !hasAnyTypes(&imps[i]) {
numToFilter = numToFilter + 1

// Skip if all media types are gone
if !hasAnyTypes(imp) {
errs = append(errs, &errortypes.BadInput{Message: fmt.Sprintf("request.imp[%d] has no supported MediaTypes. It will be ignored", i)})
updated = true
continue
}

// Handle multi-format restrictions
if !multiformatSupport && IsMultiFormat(*imp) && preferredMediaType != "" {
if err := AdjustImpForPreferredMediaType(imp, preferredMediaType); err != nil {
errs = append(errs, err)
updated = true
continue
}
}

// Move valid imp to the correct position
if updated {
imps[writeIndex] = imps[i]
}
writeIndex++
}
// If updated, return the modified slice; otherwise, return the original slice
if updated {
return true, imps[:writeIndex], errs
}
return numToFilter, errs
return false, imps, errs
}

func parseAllowedTypes(allowedTypes []openrtb_ext.BidType) (allowBanner bool, allowVideo bool, allowAudio bool, allowNative bool) {
Expand All @@ -125,24 +145,12 @@ func hasAnyTypes(imp *openrtb2.Imp) bool {
return imp.Banner != nil || imp.Video != nil || imp.Audio != nil || imp.Native != nil
}

func filterImps(imps []openrtb2.Imp, numToFilter int) ([]openrtb2.Imp, []error) {
newImps := make([]openrtb2.Imp, 0, len(imps)-numToFilter)
errs := make([]error, 0, numToFilter)
for i := 0; i < len(imps); i++ {
if hasAnyTypes(&imps[i]) {
newImps = append(newImps, imps[i])
} else {
errs = append(errs, &errortypes.BadInput{Message: fmt.Sprintf("request.imp[%d] has no supported MediaTypes. It will be ignored", i)})
}
}
return newImps, errs
}

// Structs to handle parsed bidder info, so we aren't reparsing every request
type parsedBidderInfo struct {
app parsedSupports
site parsedSupports
dooh parsedSupports
app parsedSupports
site parsedSupports
dooh parsedSupports
multiformat bool
}

type parsedSupports struct {
Expand Down Expand Up @@ -172,5 +180,101 @@ func parseBidderInfo(info config.BidderInfo) parsedBidderInfo {
parsedInfo.dooh.enabled = true
parsedInfo.dooh.banner, parsedInfo.dooh.video, parsedInfo.dooh.audio, parsedInfo.dooh.native = parseAllowedTypes(info.Capabilities.DOOH.MediaTypes)
}
parsedInfo.multiformat = IsMultiFormatSupported(info)

return parsedInfo
}

// FilterMultiformatImps filters impressions based on the preferred media type if the bidder does not support multiformat.
// It returns the updated list of impressions and any errors encountered during the filtering process.
func FilterMultiformatImps(bidRequest *openrtb2.BidRequest, preferredMediaType openrtb_ext.BidType) ([]openrtb2.Imp, []error) {
var updatedImps []openrtb2.Imp
var errs []error

for _, imp := range bidRequest.Imp {
if IsMultiFormat(imp) && preferredMediaType != "" {
if err := AdjustImpForPreferredMediaType(&imp, preferredMediaType); err != nil {
errs = append(errs, err)
continue
}
updatedImps = append(updatedImps, imp)
} else {
updatedImps = append(updatedImps, imp)
}
}

if len(updatedImps) == 0 {
errs = append(errs, &errortypes.BadInput{Message: "Bid request contains 0 impressions after filtering."})
}

return updatedImps, errs
}

// AdjustImpForPreferredMediaType modifies the given impression to retain only the preferred media type.
// It returns the updated impression and any error encountered during the adjustment process.
func AdjustImpForPreferredMediaType(imp *openrtb2.Imp, preferredMediaType openrtb_ext.BidType) error {

// Clear irrelevant media types based on the preferred media type.
switch preferredMediaType {
case openrtb_ext.BidTypeBanner:
if imp.Banner != nil {
imp.Video = nil
imp.Audio = nil
imp.Native = nil
} else {
return &errortypes.BadInput{Message: fmt.Sprintf("Imp %s does not have a valid BANNER media type.", imp.ID)}
}
case openrtb_ext.BidTypeVideo:
if imp.Video != nil {
imp.Banner = nil
imp.Audio = nil
imp.Native = nil
} else {
return &errortypes.BadInput{Message: fmt.Sprintf("Imp %s does not have a valid VIDEO media type.", imp.ID)}
}
case openrtb_ext.BidTypeAudio:
if imp.Audio != nil {
imp.Banner = nil
imp.Video = nil
imp.Native = nil
} else {
return &errortypes.BadInput{Message: fmt.Sprintf("Imp %s does not have a valid AUDIO media type.", imp.ID)}
}
case openrtb_ext.BidTypeNative:
if imp.Native != nil {
imp.Banner = nil
imp.Video = nil
imp.Audio = nil
} else {
return &errortypes.BadInput{Message: fmt.Sprintf("Imp %s does not have a valid NATIVE media type.", imp.ID)}
}
default:
return &errortypes.BadInput{Message: fmt.Sprintf("Imp %s has an invalid preferred media type: %s.", imp.ID, preferredMediaType)}
}

return nil
}

func IsMultiFormatSupported(bidderInfo config.BidderInfo) bool {
if bidderInfo.OpenRTB != nil && bidderInfo.OpenRTB.MultiformatSupported != nil {
return *bidderInfo.OpenRTB.MultiformatSupported
}
return true
}

func IsMultiFormat(imp openrtb2.Imp) bool {
count := 0
if imp.Banner != nil {
count++
}
if imp.Video != nil {
count++
}
if imp.Audio != nil {
count++
}
if imp.Native != nil {
count++
}
return count > 1
}
Loading
Loading