github.com/prebid/prebid-server@v0.275.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/v19/openrtb2" 10 "github.com/prebid/prebid-server/adapters" 11 "github.com/prebid/prebid-server/config" 12 "github.com/prebid/prebid-server/errortypes" 13 "github.com/prebid/prebid-server/macros" 14 "github.com/prebid/prebid-server/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}, nil 168 } 169 170 func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpAdvangelists, imps []openrtb2.Imp) *openrtb2.BidRequest { 171 bidRequest := *prebidBidRequest 172 bidRequest.Imp = imps 173 for idx := range bidRequest.Imp { 174 imp := &bidRequest.Imp[idx] 175 imp.TagID = params.Placement 176 } 177 if bidRequest.Site != nil { 178 // Need to copy Site as Request is a shallow copy 179 siteCopy := *bidRequest.Site 180 bidRequest.Site = &siteCopy 181 bidRequest.Site.Publisher = nil 182 bidRequest.Site.Domain = "" 183 } 184 if bidRequest.App != nil { 185 // Need to copy App as Request is a shallow copy 186 appCopy := *bidRequest.App 187 bidRequest.App = &appCopy 188 bidRequest.App.Publisher = nil 189 } 190 return &bidRequest 191 } 192 193 // Builds enpoint url based on adapter-specific pub settings from imp.ext 194 func (adapter *AdvangelistsAdapter) buildEndpointURL(params *openrtb_ext.ExtImpAdvangelists) (string, error) { 195 endpointParams := macros.EndpointTemplateParams{PublisherID: params.PublisherID} 196 return macros.ResolveMacros(adapter.EndpointTemplate, endpointParams) 197 } 198 199 // MakeBids translates advangelists bid response to prebid-server specific format 200 func (adapter *AdvangelistsAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { 201 var msg = "" 202 if response.StatusCode == http.StatusNoContent { 203 return nil, nil 204 } 205 if response.StatusCode != http.StatusOK { 206 msg = fmt.Sprintf("Unexpected http status code: %d", response.StatusCode) 207 return nil, []error{&errortypes.BadServerResponse{Message: msg}} 208 } 209 var bidResp openrtb2.BidResponse 210 if err := json.Unmarshal(response.Body, &bidResp); err != nil { 211 msg = fmt.Sprintf("Bad server response: %d", err) 212 return nil, []error{&errortypes.BadServerResponse{Message: msg}} 213 } 214 if len(bidResp.SeatBid) != 1 { 215 var msg = fmt.Sprintf("Invalid SeatBids count: %d", len(bidResp.SeatBid)) 216 return nil, []error{&errortypes.BadServerResponse{Message: msg}} 217 } 218 219 seatBid := bidResp.SeatBid[0] 220 bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid)) 221 222 for i := 0; i < len(seatBid.Bid); i++ { 223 bid := seatBid.Bid[i] 224 bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ 225 Bid: &bid, 226 BidType: getMediaTypeForImpID(bid.ImpID, internalRequest.Imp), 227 }) 228 } 229 return bidResponse, nil 230 } 231 232 // getMediaTypeForImp figures out which media type this bid is for 233 func getMediaTypeForImpID(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { 234 for _, imp := range imps { 235 if imp.ID == impID && imp.Video != nil { 236 return openrtb_ext.BidTypeVideo 237 } 238 } 239 return openrtb_ext.BidTypeBanner 240 } 241 242 // Builder builds a new instance of the Advangelists adapter for the given bidder with the given config. 243 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 244 template, err := template.New("endpointTemplate").Parse(config.Endpoint) 245 if err != nil { 246 return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) 247 } 248 249 bidder := &AdvangelistsAdapter{ 250 EndpointTemplate: template, 251 } 252 return bidder, nil 253 }