github.com/prebid/prebid-server/v2@v2.18.0/adapters/adoppler/adoppler.go (about) 1 package adoppler 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "net/http" 8 "net/url" 9 "text/template" 10 11 "github.com/prebid/openrtb/v20/openrtb2" 12 "github.com/prebid/prebid-server/v2/adapters" 13 "github.com/prebid/prebid-server/v2/config" 14 "github.com/prebid/prebid-server/v2/errortypes" 15 "github.com/prebid/prebid-server/v2/macros" 16 "github.com/prebid/prebid-server/v2/openrtb_ext" 17 ) 18 19 const DefaultClient = "app" 20 21 var bidHeaders http.Header = map[string][]string{ 22 "Accept": {"application/json"}, 23 "Content-Type": {"application/json;charset=utf-8"}, 24 "X-OpenRTB-Version": {"2.5"}, 25 } 26 27 type adsVideoExt struct { 28 Duration int `json:"duration"` 29 } 30 31 type adsImpExt struct { 32 Video *adsVideoExt `json:"video"` 33 } 34 35 type AdopplerAdapter struct { 36 endpoint *template.Template 37 } 38 39 // Builder builds a new instance of the Adoppler adapter for the given bidder with the given config. 40 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 41 template, err := template.New("endpointTemplate").Parse(config.Endpoint) 42 if err != nil { 43 return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) 44 } 45 46 bidder := &AdopplerAdapter{ 47 endpoint: template, 48 } 49 return bidder, nil 50 } 51 52 func (ads *AdopplerAdapter) MakeRequests( 53 req *openrtb2.BidRequest, 54 info *adapters.ExtraRequestInfo, 55 ) ( 56 []*adapters.RequestData, 57 []error, 58 ) { 59 if len(req.Imp) == 0 { 60 return nil, nil 61 } 62 63 var datas []*adapters.RequestData 64 var errs []error 65 for _, imp := range req.Imp { 66 ext, err := unmarshalExt(imp.Ext) 67 if err != nil { 68 errs = append(errs, &errortypes.BadInput{Message: err.Error()}) 69 continue 70 } 71 72 var r openrtb2.BidRequest = *req 73 r.ID = req.ID + "-" + ext.AdUnit 74 r.Imp = []openrtb2.Imp{imp} 75 76 body, err := json.Marshal(r) 77 if err != nil { 78 errs = append(errs, err) 79 continue 80 } 81 82 uri, err := ads.bidUri(ext) 83 if err != nil { 84 e := fmt.Sprintf("Unable to build bid URI: %s", 85 err.Error()) 86 errs = append(errs, &errortypes.BadInput{Message: e}) 87 continue 88 } 89 data := &adapters.RequestData{ 90 Method: "POST", 91 Uri: uri, 92 Body: body, 93 Headers: bidHeaders, 94 ImpIDs: openrtb_ext.GetImpIDs(r.Imp), 95 } 96 datas = append(datas, data) 97 } 98 99 return datas, errs 100 } 101 102 func (ads *AdopplerAdapter) MakeBids( 103 intReq *openrtb2.BidRequest, 104 extReq *adapters.RequestData, 105 resp *adapters.ResponseData, 106 ) ( 107 *adapters.BidderResponse, 108 []error, 109 ) { 110 if resp.StatusCode == http.StatusNoContent { 111 return nil, nil 112 } 113 if resp.StatusCode == http.StatusBadRequest { 114 return nil, []error{&errortypes.BadInput{Message: "bad request"}} 115 } 116 if resp.StatusCode != http.StatusOK { 117 err := &errortypes.BadServerResponse{ 118 Message: fmt.Sprintf("unexpected status: %d", resp.StatusCode), 119 } 120 return nil, []error{err} 121 } 122 123 var bidResp openrtb2.BidResponse 124 err := json.Unmarshal(resp.Body, &bidResp) 125 if err != nil { 126 err := &errortypes.BadServerResponse{ 127 Message: fmt.Sprintf("invalid body: %s", err.Error()), 128 } 129 return nil, []error{err} 130 } 131 132 impTypes := make(map[string]openrtb_ext.BidType) 133 for _, imp := range intReq.Imp { 134 if _, ok := impTypes[imp.ID]; ok { 135 return nil, []error{&errortypes.BadInput{ 136 Message: fmt.Sprintf("duplicate $.imp.id %s", imp.ID), 137 }} 138 } 139 if imp.Banner != nil { 140 impTypes[imp.ID] = openrtb_ext.BidTypeBanner 141 } else if imp.Video != nil { 142 impTypes[imp.ID] = openrtb_ext.BidTypeVideo 143 } else if imp.Audio != nil { 144 impTypes[imp.ID] = openrtb_ext.BidTypeAudio 145 } else if imp.Native != nil { 146 impTypes[imp.ID] = openrtb_ext.BidTypeNative 147 } else { 148 return nil, []error{&errortypes.BadInput{ 149 Message: "one of $.imp.banner, $.imp.video, $.imp.audio and $.imp.native field required", 150 }} 151 } 152 } 153 154 var bids []*adapters.TypedBid 155 for _, seatBid := range bidResp.SeatBid { 156 for _, bid := range seatBid.Bid { 157 tp, ok := impTypes[bid.ImpID] 158 if !ok { 159 err := &errortypes.BadServerResponse{ 160 Message: fmt.Sprintf("unknown impid: %s", bid.ImpID), 161 } 162 return nil, []error{err} 163 } 164 165 var bidVideo *openrtb_ext.ExtBidPrebidVideo 166 if tp == openrtb_ext.BidTypeVideo { 167 adsExt, err := unmarshalAdsExt(bid.Ext) 168 if err != nil { 169 return nil, []error{&errortypes.BadServerResponse{Message: err.Error()}} 170 } 171 if adsExt == nil || adsExt.Video == nil { 172 return nil, []error{&errortypes.BadServerResponse{ 173 Message: "$.seatbid.bid.ext.ads.video required", 174 }} 175 } 176 bidVideo = &openrtb_ext.ExtBidPrebidVideo{ 177 Duration: adsExt.Video.Duration, 178 PrimaryCategory: head(bid.Cat), 179 } 180 } 181 bids = append(bids, &adapters.TypedBid{ 182 Bid: &bid, 183 BidType: tp, 184 BidVideo: bidVideo, 185 }) 186 } 187 } 188 189 adsResp := adapters.NewBidderResponseWithBidsCapacity(len(bids)) 190 adsResp.Bids = bids 191 192 return adsResp, nil 193 } 194 195 func (ads *AdopplerAdapter) bidUri(ext *openrtb_ext.ExtImpAdoppler) (string, error) { 196 params := macros.EndpointTemplateParams{} 197 params.AdUnit = url.PathEscape(ext.AdUnit) 198 if ext.Client == "" { 199 params.AccountID = DefaultClient 200 } else { 201 params.AccountID = url.PathEscape(ext.Client) 202 } 203 204 return macros.ResolveMacros(ads.endpoint, params) 205 } 206 207 func unmarshalExt(ext json.RawMessage) (*openrtb_ext.ExtImpAdoppler, error) { 208 var bext adapters.ExtImpBidder 209 err := json.Unmarshal(ext, &bext) 210 if err != nil { 211 return nil, err 212 } 213 214 var adsExt openrtb_ext.ExtImpAdoppler 215 err = json.Unmarshal(bext.Bidder, &adsExt) 216 if err != nil { 217 return nil, err 218 } 219 220 if adsExt.AdUnit == "" { 221 return nil, errors.New("$.imp.ext.adoppler.adunit required") 222 } 223 224 return &adsExt, nil 225 } 226 227 func unmarshalAdsExt(ext json.RawMessage) (*adsImpExt, error) { 228 var e struct { 229 Ads *adsImpExt `json:"ads"` 230 } 231 err := json.Unmarshal(ext, &e) 232 233 return e.Ads, err 234 } 235 236 func head(s []string) string { 237 if len(s) == 0 { 238 return "" 239 } 240 241 return s[0] 242 }