github.com/prebid/prebid-server/v2@v2.18.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/v20/openrtb2" 11 "github.com/prebid/prebid-server/v2/adapters" 12 "github.com/prebid/prebid-server/v2/config" 13 "github.com/prebid/prebid-server/v2/errortypes" 14 "github.com/prebid/prebid-server/v2/macros" 15 "github.com/prebid/prebid-server/v2/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("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, 188 ImpIDs: openrtb_ext.GetImpIDs(imps)}, nil 189 } 190 191 func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpAdkernelAdn, imps []openrtb2.Imp) *openrtb2.BidRequest { 192 bidRequest := *prebidBidRequest 193 bidRequest.Imp = imps 194 195 if bidRequest.Site != nil { 196 // Need to copy Site as Request is a shallow copy 197 siteCopy := *bidRequest.Site 198 bidRequest.Site = &siteCopy 199 bidRequest.Site.Publisher = nil 200 bidRequest.Site.Domain = "" 201 } 202 if bidRequest.App != nil { 203 // Need to copy App as Request is a shallow copy 204 appCopy := *bidRequest.App 205 bidRequest.App = &appCopy 206 bidRequest.App.Publisher = nil 207 } 208 return &bidRequest 209 } 210 211 // Builds enpoint url based on adapter-specific pub settings from imp.ext 212 func (adapter *adkernelAdnAdapter) buildEndpointURL(params *openrtb_ext.ExtImpAdkernelAdn) (string, error) { 213 pubIDStr := strconv.Itoa(params.PublisherID) 214 endpointParams := macros.EndpointTemplateParams{PublisherID: pubIDStr} 215 return macros.ResolveMacros(adapter.EndpointTemplate, endpointParams) 216 } 217 218 // MakeBids translates adkernel bid response to prebid-server specific format 219 func (adapter *adkernelAdnAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { 220 if response.StatusCode == http.StatusNoContent { 221 return nil, nil 222 } 223 if response.StatusCode != http.StatusOK { 224 return nil, []error{ 225 newBadServerResponseError(fmt.Sprintf("Unexpected http status code: %d", response.StatusCode)), 226 } 227 } 228 var bidResp openrtb2.BidResponse 229 if err := json.Unmarshal(response.Body, &bidResp); err != nil { 230 return nil, []error{ 231 newBadServerResponseError(fmt.Sprintf("Bad server response: %d", err)), 232 } 233 } 234 if len(bidResp.SeatBid) != 1 { 235 return nil, []error{ 236 newBadServerResponseError(fmt.Sprintf("Invalid SeatBids count: %d", len(bidResp.SeatBid))), 237 } 238 } 239 240 seatBid := bidResp.SeatBid[0] 241 bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid)) 242 243 for i := 0; i < len(seatBid.Bid); i++ { 244 bid := seatBid.Bid[i] 245 bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ 246 Bid: &bid, 247 BidType: getMediaTypeForImpID(bid.ImpID, internalRequest.Imp), 248 }) 249 } 250 return bidResponse, nil 251 } 252 253 // getMediaTypeForImp figures out which media type this bid is for 254 func getMediaTypeForImpID(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { 255 for _, imp := range imps { 256 if imp.ID == impID && imp.Banner != nil { 257 return openrtb_ext.BidTypeBanner 258 } 259 } 260 return openrtb_ext.BidTypeVideo 261 } 262 263 func newBadInputError(message string) error { 264 return &errortypes.BadInput{ 265 Message: message, 266 } 267 } 268 269 func newBadServerResponseError(message string) error { 270 return &errortypes.BadServerResponse{ 271 Message: message, 272 } 273 } 274 275 // Builder builds a new instance of the AdkernelAdn adapter for the given bidder with the given config. 276 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 277 template, err := template.New("endpointTemplate").Parse(config.Endpoint) 278 if err != nil { 279 return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) 280 } 281 282 bidder := &adkernelAdnAdapter{ 283 EndpointTemplate: template, 284 } 285 return bidder, nil 286 }