github.com/prebid/prebid-server@v0.275.0/adapters/33across/33across.go (about) 1 package ttx 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 8 "github.com/prebid/openrtb/v19/adcom1" 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/openrtb_ext" 14 ) 15 16 type TtxAdapter struct { 17 endpoint string 18 } 19 20 type Ext struct { 21 Ttx impTtxExt `json:"ttx"` 22 } 23 24 type impTtxExt struct { 25 Prod string `json:"prod"` 26 Zoneid string `json:"zoneid,omitempty"` 27 } 28 29 type reqExt struct { 30 Ttx *reqTtxExt `json:"ttx,omitempty"` 31 } 32 33 type reqTtxExt struct { 34 Caller []TtxCaller `json:"caller,omitempty"` 35 } 36 37 type TtxCaller struct { 38 Name string `json:"name,omitempty"` 39 Version string `json:"version,omitempty"` 40 } 41 42 // CALLER Info used to track Prebid Server 43 // as one of the hops in the request to exchange 44 var CALLER = TtxCaller{"Prebid-Server", "n/a"} 45 46 type bidExt struct { 47 Ttx bidTtxExt `json:"ttx,omitempty"` 48 } 49 50 type bidTtxExt struct { 51 MediaType string `json:"mediaType,omitempty"` 52 } 53 54 // MakeRequests create the object for TTX Reqeust. 55 func (a *TtxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { 56 var errs []error 57 var adapterRequests []*adapters.RequestData 58 var groupedImps = make(map[string][]openrtb2.Imp) 59 60 // Construct request extension common to all imps 61 // NOTE: not blocking adapter requests on errors 62 // since request extension is optional. 63 reqExt, err := makeReqExt(request) 64 if err != nil { 65 errs = append(errs, err) 66 } 67 request.Ext = reqExt 68 69 // We only support SRA for requests containing same prod and 70 // zoneID, therefore group all imps accordingly and create a http 71 // request for each such group 72 for i := 0; i < len(request.Imp); i++ { 73 if impCopy, err := makeImps(request.Imp[i]); err == nil { 74 var impExt Ext 75 76 // Skip over imps whose extensions cannot be read since 77 // we cannot glean Prod or ZoneID which are required to 78 // group together. However let's not block request creation. 79 if err := json.Unmarshal(impCopy.Ext, &impExt); err == nil { 80 impKey := impExt.Ttx.Prod + impExt.Ttx.Zoneid 81 groupedImps[impKey] = append(groupedImps[impKey], impCopy) 82 } else { 83 errs = append(errs, err) 84 } 85 } else { 86 errs = append(errs, err) 87 } 88 } 89 90 for _, impList := range groupedImps { 91 if adapterReq, err := a.makeRequest(*request, impList); err == nil { 92 adapterRequests = append(adapterRequests, adapterReq) 93 } else { 94 errs = append(errs, err) 95 } 96 } 97 return adapterRequests, errs 98 } 99 100 func (a *TtxAdapter) makeRequest(request openrtb2.BidRequest, impList []openrtb2.Imp) (*adapters.RequestData, error) { 101 request.Imp = impList 102 103 // Last Step 104 reqJSON, err := json.Marshal(request) 105 if err != nil { 106 return nil, err 107 } 108 109 headers := http.Header{} 110 headers.Add("Content-Type", "application/json;charset=utf-8") 111 112 return &adapters.RequestData{ 113 Method: "POST", 114 Uri: a.endpoint, 115 Body: reqJSON, 116 Headers: headers, 117 }, nil 118 } 119 120 func makeImps(imp openrtb2.Imp) (openrtb2.Imp, error) { 121 if imp.Banner == nil && imp.Video == nil { 122 return openrtb2.Imp{}, &errortypes.BadInput{ 123 Message: fmt.Sprintf("Imp ID %s must have at least one of [Banner, Video] defined", imp.ID), 124 } 125 } 126 127 var bidderExt adapters.ExtImpBidder 128 if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { 129 return openrtb2.Imp{}, &errortypes.BadInput{ 130 Message: err.Error(), 131 } 132 } 133 134 var ttxExt openrtb_ext.ExtImp33across 135 if err := json.Unmarshal(bidderExt.Bidder, &ttxExt); err != nil { 136 return openrtb2.Imp{}, &errortypes.BadInput{ 137 Message: err.Error(), 138 } 139 } 140 141 var impExt Ext 142 impExt.Ttx.Prod = ttxExt.ProductId 143 144 impExt.Ttx.Zoneid = ttxExt.SiteId 145 146 if len(ttxExt.ZoneId) > 0 { 147 impExt.Ttx.Zoneid = ttxExt.ZoneId 148 } 149 150 impExtJSON, err := json.Marshal(impExt) 151 if err != nil { 152 return openrtb2.Imp{}, &errortypes.BadInput{ 153 Message: err.Error(), 154 } 155 } 156 157 imp.Ext = impExtJSON 158 159 // Validate Video if it exists 160 if imp.Video != nil { 161 videoCopy, err := validateVideoParams(imp.Video, impExt.Ttx.Prod) 162 163 imp.Video = videoCopy 164 165 if err != nil { 166 return openrtb2.Imp{}, &errortypes.BadInput{ 167 Message: err.Error(), 168 } 169 } 170 } 171 172 return imp, nil 173 } 174 175 func makeReqExt(request *openrtb2.BidRequest) ([]byte, error) { 176 var reqExt reqExt 177 178 if len(request.Ext) > 0 { 179 if err := json.Unmarshal(request.Ext, &reqExt); err != nil { 180 return nil, err 181 } 182 } 183 184 if reqExt.Ttx == nil { 185 reqExt.Ttx = &reqTtxExt{} 186 } 187 188 if reqExt.Ttx.Caller == nil { 189 reqExt.Ttx.Caller = make([]TtxCaller, 0) 190 } 191 192 reqExt.Ttx.Caller = append(reqExt.Ttx.Caller, CALLER) 193 194 return json.Marshal(reqExt) 195 } 196 197 // MakeBids make the bids for the bid response. 198 func (a *TtxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { 199 if response.StatusCode == http.StatusNoContent { 200 return nil, nil 201 } 202 203 if response.StatusCode == http.StatusBadRequest { 204 return nil, []error{&errortypes.BadInput{ 205 Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), 206 }} 207 } 208 209 if response.StatusCode != http.StatusOK { 210 return nil, []error{&errortypes.BadServerResponse{ 211 Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), 212 }} 213 } 214 215 var bidResp openrtb2.BidResponse 216 217 if err := json.Unmarshal(response.Body, &bidResp); err != nil { 218 return nil, []error{err} 219 } 220 221 bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) 222 223 for _, sb := range bidResp.SeatBid { 224 for i := range sb.Bid { 225 var bidExt bidExt 226 var bidType openrtb_ext.BidType 227 228 if err := json.Unmarshal(sb.Bid[i].Ext, &bidExt); err != nil { 229 bidType = openrtb_ext.BidTypeBanner 230 } else { 231 bidType = getBidType(bidExt) 232 } 233 234 bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ 235 Bid: &sb.Bid[i], 236 BidType: bidType, 237 }) 238 } 239 } 240 return bidResponse, nil 241 242 } 243 244 func validateVideoParams(video *openrtb2.Video, prod string) (*openrtb2.Video, error) { 245 videoCopy := *video 246 if videoCopy.W == 0 || 247 videoCopy.H == 0 || 248 videoCopy.Protocols == nil || 249 videoCopy.MIMEs == nil || 250 videoCopy.PlaybackMethod == nil { 251 252 return nil, &errortypes.BadInput{ 253 Message: "One or more invalid or missing video field(s) w, h, protocols, mimes, playbackmethod", 254 } 255 } 256 257 if videoCopy.Placement == 0 { 258 videoCopy.Placement = 2 259 } 260 261 if prod == "instream" { 262 videoCopy.Placement = 1 263 264 if videoCopy.StartDelay == nil { 265 videoCopy.StartDelay = adcom1.StartDelay.Ptr(0) 266 } 267 } 268 269 return &videoCopy, nil 270 } 271 272 func getBidType(ext bidExt) openrtb_ext.BidType { 273 if ext.Ttx.MediaType == "video" { 274 return openrtb_ext.BidTypeVideo 275 } 276 277 return openrtb_ext.BidTypeBanner 278 } 279 280 // Builder builds a new instance of the 33Across adapter for the given bidder with the given config. 281 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 282 bidder := &TtxAdapter{ 283 endpoint: config.Endpoint, 284 } 285 return bidder, nil 286 }