github.com/prebid/prebid-server/v2@v2.18.0/adapters/infoawarebidder.go (about)

     1  package adapters
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/prebid/openrtb/v20/openrtb2"
     7  	"github.com/prebid/prebid-server/v2/config"
     8  	"github.com/prebid/prebid-server/v2/errortypes"
     9  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    10  )
    11  
    12  // InfoAwareBidder wraps a Bidder to ensure all requests abide by the capabilities and
    13  // media types defined in the static/bidder-info/{bidder}.yaml file.
    14  //
    15  // It adjusts incoming requests in the following ways:
    16  //  1. If App, Site or DOOH traffic is not supported by the info file, then requests from
    17  //     those sources will be rejected before the delegate is called.
    18  //  2. If a given MediaType is not supported for the platform, then it will be set
    19  //     to nil before the request is forwarded to the delegate.
    20  //  3. Any Imps which have no MediaTypes left will be removed.
    21  //  4. If there are no valid Imps left, the delegate won't be called at all.
    22  type InfoAwareBidder struct {
    23  	Bidder
    24  	info parsedBidderInfo
    25  }
    26  
    27  // BuildInfoAwareBidder wraps a bidder to enforce inventory {site, app, dooh} and media type support.
    28  func BuildInfoAwareBidder(bidder Bidder, info config.BidderInfo) Bidder {
    29  	return &InfoAwareBidder{
    30  		Bidder: bidder,
    31  		info:   parseBidderInfo(info),
    32  	}
    33  }
    34  
    35  func (i *InfoAwareBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *ExtraRequestInfo) ([]*RequestData, []error) {
    36  	var allowedMediaTypes parsedSupports
    37  
    38  	if request.Site != nil {
    39  		if !i.info.site.enabled {
    40  			return nil, []error{&errortypes.Warning{Message: "this bidder does not support site requests"}}
    41  		}
    42  		allowedMediaTypes = i.info.site
    43  	}
    44  	if request.App != nil {
    45  		if !i.info.app.enabled {
    46  			return nil, []error{&errortypes.Warning{Message: "this bidder does not support app requests"}}
    47  		}
    48  		allowedMediaTypes = i.info.app
    49  	}
    50  	if request.DOOH != nil {
    51  		if !i.info.dooh.enabled {
    52  			return nil, []error{&errortypes.Warning{Message: "this bidder does not support dooh requests"}}
    53  		}
    54  		allowedMediaTypes = i.info.dooh
    55  	}
    56  
    57  	// Filtering imps is quite expensive (array filter with large, non-pointer elements)... but should be rare,
    58  	// because it only happens if the publisher makes a really bad request.
    59  	//
    60  	// To avoid allocating new arrays and copying in the normal case, we'll make one pass to
    61  	// see if any imps need to be removed, and another to do the removing if necessary.
    62  	numToFilter, errs := pruneImps(request.Imp, allowedMediaTypes)
    63  
    64  	// If all imps in bid request come with unsupported media types, exit
    65  	if numToFilter == len(request.Imp) {
    66  		return nil, append(errs, &errortypes.Warning{Message: "Bid request didn't contain media types supported by the bidder"})
    67  	}
    68  
    69  	if numToFilter != 0 {
    70  		// Filter out imps with unsupported media types
    71  		filteredImps, newErrs := filterImps(request.Imp, numToFilter)
    72  		request.Imp = filteredImps
    73  		errs = append(errs, newErrs...)
    74  	}
    75  	reqs, delegateErrs := i.Bidder.MakeRequests(request, reqInfo)
    76  	return reqs, append(errs, delegateErrs...)
    77  }
    78  
    79  // pruneImps trims invalid media types from each imp, and returns true if any of the
    80  // Imps have _no_ valid Media Types left.
    81  func pruneImps(imps []openrtb2.Imp, allowedTypes parsedSupports) (int, []error) {
    82  	numToFilter := 0
    83  	var errs []error
    84  	for i := 0; i < len(imps); i++ {
    85  		if !allowedTypes.banner && imps[i].Banner != nil {
    86  			imps[i].Banner = nil
    87  			errs = append(errs, &errortypes.Warning{Message: fmt.Sprintf("request.imp[%d] uses banner, but this bidder doesn't support it", i)})
    88  		}
    89  		if !allowedTypes.video && imps[i].Video != nil {
    90  			imps[i].Video = nil
    91  			errs = append(errs, &errortypes.Warning{Message: fmt.Sprintf("request.imp[%d] uses video, but this bidder doesn't support it", i)})
    92  		}
    93  		if !allowedTypes.audio && imps[i].Audio != nil {
    94  			imps[i].Audio = nil
    95  			errs = append(errs, &errortypes.Warning{Message: fmt.Sprintf("request.imp[%d] uses audio, but this bidder doesn't support it", i)})
    96  		}
    97  		if !allowedTypes.native && imps[i].Native != nil {
    98  			imps[i].Native = nil
    99  			errs = append(errs, &errortypes.Warning{Message: fmt.Sprintf("request.imp[%d] uses native, but this bidder doesn't support it", i)})
   100  		}
   101  		if !hasAnyTypes(&imps[i]) {
   102  			numToFilter = numToFilter + 1
   103  		}
   104  	}
   105  	return numToFilter, errs
   106  }
   107  
   108  func parseAllowedTypes(allowedTypes []openrtb_ext.BidType) (allowBanner bool, allowVideo bool, allowAudio bool, allowNative bool) {
   109  	for _, allowedType := range allowedTypes {
   110  		switch allowedType {
   111  		case openrtb_ext.BidTypeBanner:
   112  			allowBanner = true
   113  		case openrtb_ext.BidTypeVideo:
   114  			allowVideo = true
   115  		case openrtb_ext.BidTypeAudio:
   116  			allowAudio = true
   117  		case openrtb_ext.BidTypeNative:
   118  			allowNative = true
   119  		}
   120  	}
   121  	return
   122  }
   123  
   124  func hasAnyTypes(imp *openrtb2.Imp) bool {
   125  	return imp.Banner != nil || imp.Video != nil || imp.Audio != nil || imp.Native != nil
   126  }
   127  
   128  func filterImps(imps []openrtb2.Imp, numToFilter int) ([]openrtb2.Imp, []error) {
   129  	newImps := make([]openrtb2.Imp, 0, len(imps)-numToFilter)
   130  	errs := make([]error, 0, numToFilter)
   131  	for i := 0; i < len(imps); i++ {
   132  		if hasAnyTypes(&imps[i]) {
   133  			newImps = append(newImps, imps[i])
   134  		} else {
   135  			errs = append(errs, &errortypes.BadInput{Message: fmt.Sprintf("request.imp[%d] has no supported MediaTypes. It will be ignored", i)})
   136  		}
   137  	}
   138  	return newImps, errs
   139  }
   140  
   141  // Structs to handle parsed bidder info, so we aren't reparsing every request
   142  type parsedBidderInfo struct {
   143  	app  parsedSupports
   144  	site parsedSupports
   145  	dooh parsedSupports
   146  }
   147  
   148  type parsedSupports struct {
   149  	enabled bool
   150  	banner  bool
   151  	video   bool
   152  	audio   bool
   153  	native  bool
   154  }
   155  
   156  func parseBidderInfo(info config.BidderInfo) parsedBidderInfo {
   157  	var parsedInfo parsedBidderInfo
   158  
   159  	if info.Capabilities == nil {
   160  		return parsedInfo
   161  	}
   162  
   163  	if info.Capabilities.App != nil {
   164  		parsedInfo.app.enabled = true
   165  		parsedInfo.app.banner, parsedInfo.app.video, parsedInfo.app.audio, parsedInfo.app.native = parseAllowedTypes(info.Capabilities.App.MediaTypes)
   166  	}
   167  	if info.Capabilities.Site != nil {
   168  		parsedInfo.site.enabled = true
   169  		parsedInfo.site.banner, parsedInfo.site.video, parsedInfo.site.audio, parsedInfo.site.native = parseAllowedTypes(info.Capabilities.Site.MediaTypes)
   170  	}
   171  	if info.Capabilities.DOOH != nil {
   172  		parsedInfo.dooh.enabled = true
   173  		parsedInfo.dooh.banner, parsedInfo.dooh.video, parsedInfo.dooh.audio, parsedInfo.dooh.native = parseAllowedTypes(info.Capabilities.DOOH.MediaTypes)
   174  	}
   175  	return parsedInfo
   176  }