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