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 }