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 }