github.com/prebid/prebid-server@v0.275.0/adapters/infoawarebidder.go (about)

     1  package adapters
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/prebid/openrtb/v19/openrtb2"
     7  	"github.com/prebid/prebid-server/config"
     8  	"github.com/prebid/prebid-server/errortypes"
     9  	"github.com/prebid/prebid-server/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 or Site 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 site, app, 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  
    51  	// Filtering imps is quite expensive (array filter with large, non-pointer elements)... but should be rare,
    52  	// because it only happens if the publisher makes a really bad request.
    53  	//
    54  	// To avoid allocating new arrays and copying in the normal case, we'll make one pass to
    55  	// see if any imps need to be removed, and another to do the removing if necessary.
    56  	numToFilter, errs := pruneImps(request.Imp, allowedMediaTypes)
    57  
    58  	// If all imps in bid request come with unsupported media types, exit
    59  	if numToFilter == len(request.Imp) {
    60  		return nil, append(errs, &errortypes.Warning{Message: "Bid request didn't contain media types supported by the bidder"})
    61  	}
    62  
    63  	if numToFilter != 0 {
    64  		// Filter out imps with unsupported media types
    65  		filteredImps, newErrs := filterImps(request.Imp, numToFilter)
    66  		request.Imp = filteredImps
    67  		errs = append(errs, newErrs...)
    68  	}
    69  	reqs, delegateErrs := i.Bidder.MakeRequests(request, reqInfo)
    70  	return reqs, append(errs, delegateErrs...)
    71  }
    72  
    73  // pruneImps trims invalid media types from each imp, and returns true if any of the
    74  // Imps have _no_ valid Media Types left.
    75  func pruneImps(imps []openrtb2.Imp, allowedTypes parsedSupports) (int, []error) {
    76  	numToFilter := 0
    77  	var errs []error
    78  	for i := 0; i < len(imps); i++ {
    79  		if !allowedTypes.banner && imps[i].Banner != nil {
    80  			imps[i].Banner = nil
    81  			errs = append(errs, &errortypes.Warning{Message: fmt.Sprintf("request.imp[%d] uses banner, but this bidder doesn't support it", i)})
    82  		}
    83  		if !allowedTypes.video && imps[i].Video != nil {
    84  			imps[i].Video = nil
    85  			errs = append(errs, &errortypes.Warning{Message: fmt.Sprintf("request.imp[%d] uses video, but this bidder doesn't support it", i)})
    86  		}
    87  		if !allowedTypes.audio && imps[i].Audio != nil {
    88  			imps[i].Audio = nil
    89  			errs = append(errs, &errortypes.Warning{Message: fmt.Sprintf("request.imp[%d] uses audio, but this bidder doesn't support it", i)})
    90  		}
    91  		if !allowedTypes.native && imps[i].Native != nil {
    92  			imps[i].Native = nil
    93  			errs = append(errs, &errortypes.Warning{Message: fmt.Sprintf("request.imp[%d] uses native, but this bidder doesn't support it", i)})
    94  		}
    95  		if !hasAnyTypes(&imps[i]) {
    96  			numToFilter = numToFilter + 1
    97  		}
    98  	}
    99  	return numToFilter, errs
   100  }
   101  
   102  func parseAllowedTypes(allowedTypes []openrtb_ext.BidType) (allowBanner bool, allowVideo bool, allowAudio bool, allowNative bool) {
   103  	for _, allowedType := range allowedTypes {
   104  		switch allowedType {
   105  		case openrtb_ext.BidTypeBanner:
   106  			allowBanner = true
   107  		case openrtb_ext.BidTypeVideo:
   108  			allowVideo = true
   109  		case openrtb_ext.BidTypeAudio:
   110  			allowAudio = true
   111  		case openrtb_ext.BidTypeNative:
   112  			allowNative = true
   113  		}
   114  	}
   115  	return
   116  }
   117  
   118  func hasAnyTypes(imp *openrtb2.Imp) bool {
   119  	return imp.Banner != nil || imp.Video != nil || imp.Audio != nil || imp.Native != nil
   120  }
   121  
   122  func filterImps(imps []openrtb2.Imp, numToFilter int) ([]openrtb2.Imp, []error) {
   123  	newImps := make([]openrtb2.Imp, 0, len(imps)-numToFilter)
   124  	errs := make([]error, 0, numToFilter)
   125  	for i := 0; i < len(imps); i++ {
   126  		if hasAnyTypes(&imps[i]) {
   127  			newImps = append(newImps, imps[i])
   128  		} else {
   129  			errs = append(errs, &errortypes.BadInput{Message: fmt.Sprintf("request.imp[%d] has no supported MediaTypes. It will be ignored", i)})
   130  		}
   131  	}
   132  	return newImps, errs
   133  }
   134  
   135  // Structs to handle parsed bidder info, so we aren't reparsing every request
   136  type parsedBidderInfo struct {
   137  	app  parsedSupports
   138  	site parsedSupports
   139  }
   140  
   141  type parsedSupports struct {
   142  	enabled bool
   143  	banner  bool
   144  	video   bool
   145  	audio   bool
   146  	native  bool
   147  }
   148  
   149  func parseBidderInfo(info config.BidderInfo) parsedBidderInfo {
   150  	var parsedInfo parsedBidderInfo
   151  	if info.Capabilities != nil && info.Capabilities.App != nil {
   152  		parsedInfo.app.enabled = true
   153  		parsedInfo.app.banner, parsedInfo.app.video, parsedInfo.app.audio, parsedInfo.app.native = parseAllowedTypes(info.Capabilities.App.MediaTypes)
   154  	}
   155  	if info.Capabilities != nil && info.Capabilities.Site != nil {
   156  		parsedInfo.site.enabled = true
   157  		parsedInfo.site.banner, parsedInfo.site.video, parsedInfo.site.audio, parsedInfo.site.native = parseAllowedTypes(info.Capabilities.Site.MediaTypes)
   158  	}
   159  	return parsedInfo
   160  }