github.com/prebid/prebid-server@v0.275.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/v19/openrtb2" 12 "github.com/prebid/prebid-server/adapters" 13 "github.com/prebid/prebid-server/config" 14 "github.com/prebid/prebid-server/errortypes" 15 "github.com/prebid/prebid-server/macros" 16 "github.com/prebid/prebid-server/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{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{e}) 87 continue 88 } 89 data := &adapters.RequestData{ 90 Method: "POST", 91 Uri: uri, 92 Body: body, 93 Headers: bidHeaders, 94 } 95 datas = append(datas, data) 96 } 97 98 return datas, errs 99 } 100 101 func (ads *AdopplerAdapter) MakeBids( 102 intReq *openrtb2.BidRequest, 103 extReq *adapters.RequestData, 104 resp *adapters.ResponseData, 105 ) ( 106 *adapters.BidderResponse, 107 []error, 108 ) { 109 if resp.StatusCode == http.StatusNoContent { 110 return nil, nil 111 } 112 if resp.StatusCode == http.StatusBadRequest { 113 return nil, []error{&errortypes.BadInput{"bad request"}} 114 } 115 if resp.StatusCode != http.StatusOK { 116 err := &errortypes.BadServerResponse{ 117 fmt.Sprintf("unexpected status: %d", resp.StatusCode), 118 } 119 return nil, []error{err} 120 } 121 122 var bidResp openrtb2.BidResponse 123 err := json.Unmarshal(resp.Body, &bidResp) 124 if err != nil { 125 err := &errortypes.BadServerResponse{ 126 fmt.Sprintf("invalid body: %s", err.Error()), 127 } 128 return nil, []error{err} 129 } 130 131 impTypes := make(map[string]openrtb_ext.BidType) 132 for _, imp := range intReq.Imp { 133 if _, ok := impTypes[imp.ID]; ok { 134 return nil, []error{&errortypes.BadInput{ 135 fmt.Sprintf("duplicate $.imp.id %s", imp.ID), 136 }} 137 } 138 if imp.Banner != nil { 139 impTypes[imp.ID] = openrtb_ext.BidTypeBanner 140 } else if imp.Video != nil { 141 impTypes[imp.ID] = openrtb_ext.BidTypeVideo 142 } else if imp.Audio != nil { 143 impTypes[imp.ID] = openrtb_ext.BidTypeAudio 144 } else if imp.Native != nil { 145 impTypes[imp.ID] = openrtb_ext.BidTypeNative 146 } else { 147 return nil, []error{&errortypes.BadInput{ 148 "one of $.imp.banner, $.imp.video, $.imp.audio and $.imp.native field required", 149 }} 150 } 151 } 152 153 var bids []*adapters.TypedBid 154 for _, seatBid := range bidResp.SeatBid { 155 for _, bid := range seatBid.Bid { 156 tp, ok := impTypes[bid.ImpID] 157 if !ok { 158 err := &errortypes.BadServerResponse{ 159 fmt.Sprintf("unknown impid: %s", bid.ImpID), 160 } 161 return nil, []error{err} 162 } 163 164 var bidVideo *openrtb_ext.ExtBidPrebidVideo 165 if tp == openrtb_ext.BidTypeVideo { 166 adsExt, err := unmarshalAdsExt(bid.Ext) 167 if err != nil { 168 return nil, []error{&errortypes.BadServerResponse{err.Error()}} 169 } 170 if adsExt == nil || adsExt.Video == nil { 171 return nil, []error{&errortypes.BadServerResponse{ 172 "$.seatbid.bid.ext.ads.video required", 173 }} 174 } 175 bidVideo = &openrtb_ext.ExtBidPrebidVideo{ 176 Duration: adsExt.Video.Duration, 177 PrimaryCategory: head(bid.Cat), 178 } 179 } 180 bids = append(bids, &adapters.TypedBid{ 181 Bid: &bid, 182 BidType: tp, 183 BidVideo: bidVideo, 184 }) 185 } 186 } 187 188 adsResp := adapters.NewBidderResponseWithBidsCapacity(len(bids)) 189 adsResp.Bids = bids 190 191 return adsResp, nil 192 } 193 194 func (ads *AdopplerAdapter) bidUri(ext *openrtb_ext.ExtImpAdoppler) (string, error) { 195 params := macros.EndpointTemplateParams{} 196 params.AdUnit = url.PathEscape(ext.AdUnit) 197 if ext.Client == "" { 198 params.AccountID = DefaultClient 199 } else { 200 params.AccountID = url.PathEscape(ext.Client) 201 } 202 203 return macros.ResolveMacros(ads.endpoint, params) 204 } 205 206 func unmarshalExt(ext json.RawMessage) (*openrtb_ext.ExtImpAdoppler, error) { 207 var bext adapters.ExtImpBidder 208 err := json.Unmarshal(ext, &bext) 209 if err != nil { 210 return nil, err 211 } 212 213 var adsExt openrtb_ext.ExtImpAdoppler 214 err = json.Unmarshal(bext.Bidder, &adsExt) 215 if err != nil { 216 return nil, err 217 } 218 219 if adsExt.AdUnit == "" { 220 return nil, errors.New("$.imp.ext.adoppler.adunit required") 221 } 222 223 return &adsExt, nil 224 } 225 226 func unmarshalAdsExt(ext json.RawMessage) (*adsImpExt, error) { 227 var e struct { 228 Ads *adsImpExt `json:"ads"` 229 } 230 err := json.Unmarshal(ext, &e) 231 232 return e.Ads, err 233 } 234 235 func head(s []string) string { 236 if len(s) == 0 { 237 return "" 238 } 239 240 return s[0] 241 }