github.com/prebid/prebid-server@v0.275.0/adapters/adkernelAdn/adkernelAdn.go (about) 1 package adkernelAdn 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "strconv" 8 "text/template" 9 10 "github.com/prebid/openrtb/v19/openrtb2" 11 "github.com/prebid/prebid-server/adapters" 12 "github.com/prebid/prebid-server/config" 13 "github.com/prebid/prebid-server/errortypes" 14 "github.com/prebid/prebid-server/macros" 15 "github.com/prebid/prebid-server/openrtb_ext" 16 ) 17 18 type adkernelAdnAdapter struct { 19 EndpointTemplate *template.Template 20 } 21 22 // MakeRequests prepares request information for prebid-server core 23 func (adapter *adkernelAdnAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { 24 errs := make([]error, 0, len(request.Imp)) 25 if len(request.Imp) == 0 { 26 errs = append(errs, newBadInputError("No impression in the bid request")) 27 return nil, errs 28 } 29 imps, impExts, err := getImpressionsInfo(request.Imp) 30 if len(imps) == 0 { 31 return nil, err 32 } 33 errs = append(errs, err...) 34 35 pub2impressions, dispErrors := dispatchImpressions(imps, impExts) 36 if len(dispErrors) > 0 { 37 errs = append(errs, dispErrors...) 38 } 39 if len(pub2impressions) == 0 { 40 return nil, errs 41 } 42 result := make([]*adapters.RequestData, 0, len(pub2impressions)) 43 for k, imps := range pub2impressions { 44 bidRequest, err := adapter.buildAdapterRequest(request, &k, imps) 45 if err != nil { 46 errs = append(errs, err) 47 } else { 48 result = append(result, bidRequest) 49 } 50 } 51 return result, errs 52 } 53 54 // getImpressionsInfo checks each impression for validity and returns impressions copy with corresponding exts 55 func getImpressionsInfo(imps []openrtb2.Imp) ([]openrtb2.Imp, []openrtb_ext.ExtImpAdkernelAdn, []error) { 56 impsCount := len(imps) 57 errors := make([]error, 0, impsCount) 58 resImps := make([]openrtb2.Imp, 0, impsCount) 59 resImpExts := make([]openrtb_ext.ExtImpAdkernelAdn, 0, impsCount) 60 61 for _, imp := range imps { 62 impExt, err := getImpressionExt(&imp) 63 if err != nil { 64 errors = append(errors, err) 65 continue 66 } 67 if err := validateImpression(&imp, impExt); err != nil { 68 errors = append(errors, err) 69 continue 70 } 71 resImps = append(resImps, imp) 72 resImpExts = append(resImpExts, *impExt) 73 74 } 75 return resImps, resImpExts, errors 76 } 77 78 func validateImpression(imp *openrtb2.Imp, impExt *openrtb_ext.ExtImpAdkernelAdn) error { 79 if impExt.PublisherID < 1 { 80 return newBadInputError(fmt.Sprintf("Invalid pubId value. Ignoring imp id=%s", imp.ID)) 81 } 82 if imp.Video == nil && imp.Banner == nil { 83 return newBadInputError(fmt.Sprintf("Invalid imp with id=%s. Expected imp.banner or imp.video", imp.ID)) 84 } 85 return nil 86 } 87 88 // Group impressions by AdKernel-specific parameters `pubId` & `host` 89 func dispatchImpressions(imps []openrtb2.Imp, impsExt []openrtb_ext.ExtImpAdkernelAdn) (map[openrtb_ext.ExtImpAdkernelAdn][]openrtb2.Imp, []error) { 90 res := make(map[openrtb_ext.ExtImpAdkernelAdn][]openrtb2.Imp) 91 errors := make([]error, 0) 92 for idx := range imps { 93 imp := imps[idx] 94 err := compatImpression(&imp) 95 if err != nil { 96 errors = append(errors, err) 97 continue 98 } 99 impExt := impsExt[idx] 100 if res[impExt] == nil { 101 res[impExt] = make([]openrtb2.Imp, 0) 102 } 103 res[impExt] = append(res[impExt], imp) 104 } 105 return res, errors 106 } 107 108 // Alter impression info to comply with adkernel platform requirements 109 func compatImpression(imp *openrtb2.Imp) error { 110 imp.Ext = nil //do not forward ext to adkernel platform 111 if imp.Banner != nil { 112 return compatBannerImpression(imp) 113 } 114 if imp.Video != nil { 115 return compatVideoImpression(imp) 116 } 117 return newBadInputError("Unsupported impression has been received") 118 } 119 120 func compatBannerImpression(imp *openrtb2.Imp) error { 121 // Create a copy of the banner, since imp is a shallow copy of the original. 122 bannerCopy := *imp.Banner 123 banner := &bannerCopy 124 //As banner.w/h are required fields for adkernelAdn platform - take the first format entry 125 if banner.W == nil && banner.H == nil { 126 if len(banner.Format) == 0 { 127 return newBadInputError(fmt.Sprintf("Expected at least one banner.format entry or explicit w/h")) 128 } 129 format := banner.Format[0] 130 banner.Format = banner.Format[1:] 131 banner.W = &format.W 132 banner.H = &format.H 133 imp.Banner = banner 134 } 135 136 imp.Video = nil 137 imp.Native = nil 138 imp.Audio = nil 139 140 return nil 141 } 142 143 func compatVideoImpression(imp *openrtb2.Imp) error { 144 imp.Banner = nil 145 imp.Audio = nil 146 imp.Native = nil 147 return nil 148 } 149 150 func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpAdkernelAdn, error) { 151 var bidderExt adapters.ExtImpBidder 152 if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { 153 return nil, &errortypes.BadInput{ 154 Message: err.Error(), 155 } 156 } 157 var adkernelAdnExt openrtb_ext.ExtImpAdkernelAdn 158 if err := json.Unmarshal(bidderExt.Bidder, &adkernelAdnExt); err != nil { 159 return nil, &errortypes.BadInput{ 160 Message: err.Error(), 161 } 162 } 163 return &adkernelAdnExt, nil 164 } 165 166 func (adapter *adkernelAdnAdapter) buildAdapterRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpAdkernelAdn, imps []openrtb2.Imp) (*adapters.RequestData, error) { 167 newBidRequest := createBidRequest(prebidBidRequest, params, imps) 168 reqJSON, err := json.Marshal(newBidRequest) 169 if err != nil { 170 return nil, err 171 } 172 173 headers := http.Header{} 174 headers.Add("Content-Type", "application/json;charset=utf-8") 175 headers.Add("Accept", "application/json") 176 headers.Add("x-openrtb-version", "2.5") 177 178 url, err := adapter.buildEndpointURL(params) 179 if err != nil { 180 return nil, err 181 } 182 183 return &adapters.RequestData{ 184 Method: "POST", 185 Uri: url, 186 Body: reqJSON, 187 Headers: headers}, nil 188 } 189 190 func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpAdkernelAdn, imps []openrtb2.Imp) *openrtb2.BidRequest { 191 bidRequest := *prebidBidRequest 192 bidRequest.Imp = imps 193 194 if bidRequest.Site != nil { 195 // Need to copy Site as Request is a shallow copy 196 siteCopy := *bidRequest.Site 197 bidRequest.Site = &siteCopy 198 bidRequest.Site.Publisher = nil 199 bidRequest.Site.Domain = "" 200 } 201 if bidRequest.App != nil { 202 // Need to copy App as Request is a shallow copy 203 appCopy := *bidRequest.App 204 bidRequest.App = &appCopy 205 bidRequest.App.Publisher = nil 206 } 207 return &bidRequest 208 } 209 210 // Builds enpoint url based on adapter-specific pub settings from imp.ext 211 func (adapter *adkernelAdnAdapter) buildEndpointURL(params *openrtb_ext.ExtImpAdkernelAdn) (string, error) { 212 pubIDStr := strconv.Itoa(params.PublisherID) 213 endpointParams := macros.EndpointTemplateParams{PublisherID: pubIDStr} 214 return macros.ResolveMacros(adapter.EndpointTemplate, endpointParams) 215 } 216 217 // MakeBids translates adkernel bid response to prebid-server specific format 218 func (adapter *adkernelAdnAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { 219 if response.StatusCode == http.StatusNoContent { 220 return nil, nil 221 } 222 if response.StatusCode != http.StatusOK { 223 return nil, []error{ 224 newBadServerResponseError(fmt.Sprintf("Unexpected http status code: %d", response.StatusCode)), 225 } 226 } 227 var bidResp openrtb2.BidResponse 228 if err := json.Unmarshal(response.Body, &bidResp); err != nil { 229 return nil, []error{ 230 newBadServerResponseError(fmt.Sprintf("Bad server response: %d", err)), 231 } 232 } 233 if len(bidResp.SeatBid) != 1 { 234 return nil, []error{ 235 newBadServerResponseError(fmt.Sprintf("Invalid SeatBids count: %d", len(bidResp.SeatBid))), 236 } 237 } 238 239 seatBid := bidResp.SeatBid[0] 240 bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid)) 241 242 for i := 0; i < len(seatBid.Bid); i++ { 243 bid := seatBid.Bid[i] 244 bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ 245 Bid: &bid, 246 BidType: getMediaTypeForImpID(bid.ImpID, internalRequest.Imp), 247 }) 248 } 249 return bidResponse, nil 250 } 251 252 // getMediaTypeForImp figures out which media type this bid is for 253 func getMediaTypeForImpID(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { 254 for _, imp := range imps { 255 if imp.ID == impID && imp.Banner != nil { 256 return openrtb_ext.BidTypeBanner 257 } 258 } 259 return openrtb_ext.BidTypeVideo 260 } 261 262 func newBadInputError(message string) error { 263 return &errortypes.BadInput{ 264 Message: message, 265 } 266 } 267 268 func newBadServerResponseError(message string) error { 269 return &errortypes.BadServerResponse{ 270 Message: message, 271 } 272 } 273 274 // Builder builds a new instance of the AdkernelAdn adapter for the given bidder with the given config. 275 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 276 template, err := template.New("endpointTemplate").Parse(config.Endpoint) 277 if err != nil { 278 return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) 279 } 280 281 bidder := &adkernelAdnAdapter{ 282 EndpointTemplate: template, 283 } 284 return bidder, nil 285 }