github.com/prebid/prebid-server/v2@v2.18.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/v20/adcom1" 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/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 ImpIDs: openrtb_ext.GetImpIDs(request.Imp), 118 }, nil 119 } 120 121 func makeImps(imp openrtb2.Imp) (openrtb2.Imp, error) { 122 if imp.Banner == nil && imp.Video == nil { 123 return openrtb2.Imp{}, &errortypes.BadInput{ 124 Message: fmt.Sprintf("Imp ID %s must have at least one of [Banner, Video] defined", imp.ID), 125 } 126 } 127 128 var bidderExt adapters.ExtImpBidder 129 if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { 130 return openrtb2.Imp{}, &errortypes.BadInput{ 131 Message: err.Error(), 132 } 133 } 134 135 var ttxExt openrtb_ext.ExtImp33across 136 if err := json.Unmarshal(bidderExt.Bidder, &ttxExt); err != nil { 137 return openrtb2.Imp{}, &errortypes.BadInput{ 138 Message: err.Error(), 139 } 140 } 141 142 var impExt Ext 143 impExt.Ttx.Prod = ttxExt.ProductId 144 145 impExt.Ttx.Zoneid = ttxExt.SiteId 146 147 if len(ttxExt.ZoneId) > 0 { 148 impExt.Ttx.Zoneid = ttxExt.ZoneId 149 } 150 151 impExtJSON, err := json.Marshal(impExt) 152 if err != nil { 153 return openrtb2.Imp{}, &errortypes.BadInput{ 154 Message: err.Error(), 155 } 156 } 157 158 imp.Ext = impExtJSON 159 160 // Validate Video if it exists 161 if imp.Video != nil { 162 videoCopy, err := validateVideoParams(imp.Video, impExt.Ttx.Prod) 163 164 imp.Video = videoCopy 165 166 if err != nil { 167 return openrtb2.Imp{}, &errortypes.BadInput{ 168 Message: err.Error(), 169 } 170 } 171 } 172 173 return imp, nil 174 } 175 176 func makeReqExt(request *openrtb2.BidRequest) ([]byte, error) { 177 var reqExt reqExt 178 179 if len(request.Ext) > 0 { 180 if err := json.Unmarshal(request.Ext, &reqExt); err != nil { 181 return nil, err 182 } 183 } 184 185 if reqExt.Ttx == nil { 186 reqExt.Ttx = &reqTtxExt{} 187 } 188 189 if reqExt.Ttx.Caller == nil { 190 reqExt.Ttx.Caller = make([]TtxCaller, 0) 191 } 192 193 reqExt.Ttx.Caller = append(reqExt.Ttx.Caller, CALLER) 194 195 return json.Marshal(reqExt) 196 } 197 198 // MakeBids make the bids for the bid response. 199 func (a *TtxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { 200 if response.StatusCode == http.StatusNoContent { 201 return nil, nil 202 } 203 204 if response.StatusCode == http.StatusBadRequest { 205 return nil, []error{&errortypes.BadInput{ 206 Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), 207 }} 208 } 209 210 if response.StatusCode != http.StatusOK { 211 return nil, []error{&errortypes.BadServerResponse{ 212 Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), 213 }} 214 } 215 216 var bidResp openrtb2.BidResponse 217 218 if err := json.Unmarshal(response.Body, &bidResp); err != nil { 219 return nil, []error{err} 220 } 221 222 bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) 223 224 for _, sb := range bidResp.SeatBid { 225 for i := range sb.Bid { 226 var bidExt bidExt 227 var bidType openrtb_ext.BidType 228 229 if err := json.Unmarshal(sb.Bid[i].Ext, &bidExt); err != nil { 230 bidType = openrtb_ext.BidTypeBanner 231 } else { 232 bidType = getBidType(bidExt) 233 } 234 235 bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ 236 Bid: &sb.Bid[i], 237 BidType: bidType, 238 }) 239 } 240 } 241 return bidResponse, nil 242 243 } 244 245 func validateVideoParams(video *openrtb2.Video, prod string) (*openrtb2.Video, error) { 246 videoCopy := *video 247 if (videoCopy.W == nil || *videoCopy.W == 0) || 248 (videoCopy.H == nil || *videoCopy.H == 0) || 249 videoCopy.Protocols == nil || 250 videoCopy.MIMEs == nil || 251 videoCopy.PlaybackMethod == nil { 252 253 return nil, &errortypes.BadInput{ 254 Message: "One or more invalid or missing video field(s) w, h, protocols, mimes, playbackmethod", 255 } 256 } 257 258 if videoCopy.Placement == 0 { 259 videoCopy.Placement = 2 260 } 261 262 if prod == "instream" { 263 videoCopy.Placement = 1 264 265 if videoCopy.StartDelay == nil { 266 videoCopy.StartDelay = adcom1.StartDelay.Ptr(0) 267 } 268 } 269 270 return &videoCopy, nil 271 } 272 273 func getBidType(ext bidExt) openrtb_ext.BidType { 274 if ext.Ttx.MediaType == "video" { 275 return openrtb_ext.BidTypeVideo 276 } 277 278 return openrtb_ext.BidTypeBanner 279 } 280 281 // Builder builds a new instance of the 33Across adapter for the given bidder with the given config. 282 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 283 bidder := &TtxAdapter{ 284 endpoint: config.Endpoint, 285 } 286 return bidder, nil 287 }