github.com/prebid/prebid-server/v2@v2.18.0/adapters/unicorn/unicorn.go (about) 1 package unicorn 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "net/http" 8 9 "github.com/buger/jsonparser" 10 "github.com/prebid/openrtb/v20/openrtb2" 11 "github.com/prebid/prebid-server/v2/adapters" 12 "github.com/prebid/prebid-server/v2/config" 13 "github.com/prebid/prebid-server/v2/errortypes" 14 "github.com/prebid/prebid-server/v2/openrtb_ext" 15 ) 16 17 type adapter struct { 18 endpoint string 19 } 20 21 // unicornImpExt is imp ext for UNICORN 22 type unicornImpExt struct { 23 Context *unicornImpExtContext `json:"context,omitempty"` 24 Bidder openrtb_ext.ExtImpUnicorn `json:"bidder"` 25 } 26 27 type unicornImpExtContext struct { 28 Data interface{} `json:"data,omitempty"` 29 } 30 31 // unicornExt is ext for UNICORN 32 type unicornExt struct { 33 Prebid *openrtb_ext.ExtImpPrebid `json:"prebid,omitempty"` 34 AccountID int64 `json:"accountId,omitempty"` 35 } 36 37 // Builder builds a new instance of the UNICORN adapter for the given bidder with the given config. 38 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 39 bidder := &adapter{ 40 endpoint: config.Endpoint, 41 } 42 return bidder, nil 43 } 44 45 // MakeRequests makes the HTTP requests which should be made to fetch bids. 46 func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { 47 var extRegs openrtb_ext.ExtRegs 48 if request.Regs != nil { 49 if request.Regs.COPPA == 1 { 50 return nil, []error{&errortypes.BadInput{ 51 Message: "COPPA is not supported", 52 }} 53 } 54 if err := json.Unmarshal(request.Regs.Ext, &extRegs); err == nil { 55 if extRegs.GDPR != nil && (*extRegs.GDPR == 1) { 56 return nil, []error{&errortypes.BadInput{ 57 Message: "GDPR is not supported", 58 }} 59 } 60 if extRegs.USPrivacy != "" { 61 return nil, []error{&errortypes.BadInput{ 62 Message: "CCPA is not supported", 63 }} 64 } 65 } 66 } 67 68 err := modifyImps(request) 69 if err != nil { 70 return nil, []error{err} 71 } 72 73 if err := modifyApp(request); err != nil { 74 return nil, []error{err} 75 } 76 77 var modifiableSource openrtb2.Source 78 if request.Source != nil { 79 modifiableSource = *request.Source 80 } else { 81 modifiableSource = openrtb2.Source{} 82 } 83 modifiableSource.Ext = setSourceExt() 84 request.Source = &modifiableSource 85 86 request.Ext, err = setExt(request) 87 if err != nil { 88 return nil, []error{err} 89 } 90 91 requestJSON, err := json.Marshal(request) 92 if err != nil { 93 return nil, []error{err} 94 } 95 96 requestData := &adapters.RequestData{ 97 Method: "POST", 98 Uri: a.endpoint, 99 Body: requestJSON, 100 Headers: getHeaders(request), 101 ImpIDs: openrtb_ext.GetImpIDs(request.Imp), 102 } 103 104 return []*adapters.RequestData{requestData}, nil 105 } 106 107 func getHeaders(request *openrtb2.BidRequest) http.Header { 108 headers := http.Header{} 109 headers.Add("Content-Type", "application/json;charset=utf-8") 110 headers.Add("Accept", "application/json") 111 headers.Add("X-Openrtb-Version", "2.5") 112 113 if request.Device != nil { 114 if len(request.Device.UA) > 0 { 115 headers.Add("User-Agent", request.Device.UA) 116 } 117 118 if len(request.Device.IPv6) > 0 { 119 headers.Add("X-Forwarded-For", request.Device.IPv6) 120 } 121 122 if len(request.Device.IP) > 0 { 123 headers.Add("X-Forwarded-For", request.Device.IP) 124 } 125 } 126 127 return headers 128 } 129 130 func modifyImps(request *openrtb2.BidRequest) error { 131 for i := 0; i < len(request.Imp); i++ { 132 imp := &request.Imp[i] 133 134 var ext unicornImpExt 135 err := json.Unmarshal(imp.Ext, &ext) 136 137 if err != nil { 138 return &errortypes.BadInput{ 139 Message: fmt.Sprintf("Error while decoding imp[%d].ext: %s", i, err), 140 } 141 } 142 143 if ext.Bidder.PlacementID == "" { 144 ext.Bidder.PlacementID, err = getStoredRequestImpID(imp) 145 if err != nil { 146 return &errortypes.BadInput{ 147 Message: fmt.Sprintf("Error get StoredRequestImpID from imp[%d]: %s", i, err), 148 } 149 } 150 } 151 152 imp.Ext, err = json.Marshal(ext) 153 if err != nil { 154 return &errortypes.BadInput{ 155 Message: fmt.Sprintf("Error while encoding imp[%d].ext: %s", i, err), 156 } 157 } 158 159 secure := int8(1) 160 imp.Secure = &secure 161 imp.TagID = ext.Bidder.PlacementID 162 } 163 return nil 164 } 165 166 func getStoredRequestImpID(imp *openrtb2.Imp) (string, error) { 167 v, err := jsonparser.GetString(imp.Ext, "prebid", "storedrequest", "id") 168 169 if err != nil { 170 return "", fmt.Errorf("stored request id not found: %s", err) 171 } 172 173 return v, nil 174 } 175 176 func setSourceExt() json.RawMessage { 177 return json.RawMessage(`{"stype": "prebid_server_uncn", "bidder": "unicorn"}`) 178 } 179 180 func modifyApp(request *openrtb2.BidRequest) error { 181 if request.App == nil { 182 return errors.New("request app is required") 183 } 184 185 modifiableApp := *request.App 186 187 mediaId, err := jsonparser.GetString(request.Imp[0].Ext, "bidder", "mediaId") 188 if err == nil { 189 modifiableApp.ID = mediaId 190 } 191 192 publisherId, err := jsonparser.GetString(request.Imp[0].Ext, "bidder", "publisherId") 193 if err == nil { 194 var publisher openrtb2.Publisher 195 if modifiableApp.Publisher != nil { 196 publisher = *modifiableApp.Publisher 197 } else { 198 publisher = openrtb2.Publisher{} 199 } 200 201 publisher.ID = publisherId 202 203 modifiableApp.Publisher = &publisher 204 } 205 206 request.App = &modifiableApp 207 return nil 208 } 209 210 func setExt(request *openrtb2.BidRequest) (json.RawMessage, error) { 211 accountID, err := jsonparser.GetInt(request.Imp[0].Ext, "bidder", "accountId") 212 if err != nil { 213 return nil, fmt.Errorf("accountId field is required") 214 } 215 216 var decodedExt *unicornExt 217 err = json.Unmarshal(request.Ext, &decodedExt) 218 if err != nil { 219 decodedExt = &unicornExt{ 220 Prebid: nil, 221 } 222 } 223 decodedExt.AccountID = accountID 224 225 ext, err := json.Marshal(decodedExt) 226 if err != nil { 227 return nil, &errortypes.BadInput{ 228 Message: fmt.Sprintf("Error while encoding ext, err: %s", err), 229 } 230 } 231 return ext, nil 232 } 233 234 // MakeBids unpacks the server's response into Bids. 235 func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { 236 237 if responseData.StatusCode == http.StatusNoContent { 238 return nil, nil 239 } 240 241 if responseData.StatusCode == http.StatusBadRequest { 242 err := &errortypes.BadInput{ 243 Message: "Unexpected http status code: 400", 244 } 245 return nil, []error{err} 246 } 247 248 if responseData.StatusCode != http.StatusOK { 249 err := &errortypes.BadServerResponse{ 250 Message: fmt.Sprintf("Unexpected http status code: %d", responseData.StatusCode), 251 } 252 return nil, []error{err} 253 } 254 255 var response openrtb2.BidResponse 256 if err := json.Unmarshal(responseData.Body, &response); err != nil { 257 return nil, []error{err} 258 } 259 260 bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) 261 bidResponse.Currency = response.Cur 262 for _, seatBid := range response.SeatBid { 263 for _, bid := range seatBid.Bid { 264 bid := bid 265 var bidType openrtb_ext.BidType 266 for _, imp := range request.Imp { 267 if imp.ID == bid.ImpID { 268 if imp.Banner != nil { 269 bidType = openrtb_ext.BidTypeBanner 270 } 271 } 272 } 273 b := &adapters.TypedBid{ 274 Bid: &bid, 275 BidType: bidType, 276 } 277 bidResponse.Bids = append(bidResponse.Bids, b) 278 } 279 } 280 return bidResponse, nil 281 }