github.com/prebid/prebid-server@v0.275.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/v19/openrtb2" 11 "github.com/prebid/prebid-server/adapters" 12 "github.com/prebid/prebid-server/config" 13 "github.com/prebid/prebid-server/errortypes" 14 "github.com/prebid/prebid-server/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 } 102 103 return []*adapters.RequestData{requestData}, nil 104 } 105 106 func getHeaders(request *openrtb2.BidRequest) http.Header { 107 headers := http.Header{} 108 headers.Add("Content-Type", "application/json;charset=utf-8") 109 headers.Add("Accept", "application/json") 110 headers.Add("X-Openrtb-Version", "2.5") 111 112 if request.Device != nil { 113 if len(request.Device.UA) > 0 { 114 headers.Add("User-Agent", request.Device.UA) 115 } 116 117 if len(request.Device.IPv6) > 0 { 118 headers.Add("X-Forwarded-For", request.Device.IPv6) 119 } 120 121 if len(request.Device.IP) > 0 { 122 headers.Add("X-Forwarded-For", request.Device.IP) 123 } 124 } 125 126 return headers 127 } 128 129 func modifyImps(request *openrtb2.BidRequest) error { 130 for i := 0; i < len(request.Imp); i++ { 131 imp := &request.Imp[i] 132 133 var ext unicornImpExt 134 err := json.Unmarshal(imp.Ext, &ext) 135 136 if err != nil { 137 return &errortypes.BadInput{ 138 Message: fmt.Sprintf("Error while decoding imp[%d].ext: %s", i, err), 139 } 140 } 141 142 if ext.Bidder.PlacementID == "" { 143 ext.Bidder.PlacementID, err = getStoredRequestImpID(imp) 144 if err != nil { 145 return &errortypes.BadInput{ 146 Message: fmt.Sprintf("Error get StoredRequestImpID from imp[%d]: %s", i, err), 147 } 148 } 149 } 150 151 imp.Ext, err = json.Marshal(ext) 152 if err != nil { 153 return &errortypes.BadInput{ 154 Message: fmt.Sprintf("Error while encoding imp[%d].ext: %s", i, err), 155 } 156 } 157 158 secure := int8(1) 159 imp.Secure = &secure 160 imp.TagID = ext.Bidder.PlacementID 161 } 162 return nil 163 } 164 165 func getStoredRequestImpID(imp *openrtb2.Imp) (string, error) { 166 v, err := jsonparser.GetString(imp.Ext, "prebid", "storedrequest", "id") 167 168 if err != nil { 169 return "", fmt.Errorf("stored request id not found: %s", err) 170 } 171 172 return v, nil 173 } 174 175 func setSourceExt() json.RawMessage { 176 return json.RawMessage(`{"stype": "prebid_server_uncn", "bidder": "unicorn"}`) 177 } 178 179 func modifyApp(request *openrtb2.BidRequest) error { 180 if request.App == nil { 181 return errors.New("request app is required") 182 } 183 184 modifiableApp := *request.App 185 186 mediaId, err := jsonparser.GetString(request.Imp[0].Ext, "bidder", "mediaId") 187 if err == nil { 188 modifiableApp.ID = mediaId 189 } 190 191 publisherId, err := jsonparser.GetString(request.Imp[0].Ext, "bidder", "publisherId") 192 if err == nil { 193 var publisher openrtb2.Publisher 194 if modifiableApp.Publisher != nil { 195 publisher = *modifiableApp.Publisher 196 } else { 197 publisher = openrtb2.Publisher{} 198 } 199 200 publisher.ID = publisherId 201 202 modifiableApp.Publisher = &publisher 203 } 204 205 request.App = &modifiableApp 206 return nil 207 } 208 209 func setExt(request *openrtb2.BidRequest) (json.RawMessage, error) { 210 accountID, err := jsonparser.GetInt(request.Imp[0].Ext, "bidder", "accountId") 211 if err != nil { 212 return nil, fmt.Errorf("accountId field is required") 213 } 214 215 var decodedExt *unicornExt 216 err = json.Unmarshal(request.Ext, &decodedExt) 217 if err != nil { 218 decodedExt = &unicornExt{ 219 Prebid: nil, 220 } 221 } 222 decodedExt.AccountID = accountID 223 224 ext, err := json.Marshal(decodedExt) 225 if err != nil { 226 return nil, &errortypes.BadInput{ 227 Message: fmt.Sprintf("Error while encoding ext, err: %s", err), 228 } 229 } 230 return ext, nil 231 } 232 233 // MakeBids unpacks the server's response into Bids. 234 func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { 235 236 if responseData.StatusCode == http.StatusNoContent { 237 return nil, nil 238 } 239 240 if responseData.StatusCode == http.StatusBadRequest { 241 err := &errortypes.BadInput{ 242 Message: "Unexpected http status code: 400", 243 } 244 return nil, []error{err} 245 } 246 247 if responseData.StatusCode != http.StatusOK { 248 err := &errortypes.BadServerResponse{ 249 Message: fmt.Sprintf("Unexpected http status code: %d", responseData.StatusCode), 250 } 251 return nil, []error{err} 252 } 253 254 var response openrtb2.BidResponse 255 if err := json.Unmarshal(responseData.Body, &response); err != nil { 256 return nil, []error{err} 257 } 258 259 bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) 260 bidResponse.Currency = response.Cur 261 for _, seatBid := range response.SeatBid { 262 for _, bid := range seatBid.Bid { 263 bid := bid 264 var bidType openrtb_ext.BidType 265 for _, imp := range request.Imp { 266 if imp.ID == bid.ImpID { 267 if imp.Banner != nil { 268 bidType = openrtb_ext.BidTypeBanner 269 } 270 } 271 } 272 b := &adapters.TypedBid{ 273 Bid: &bid, 274 BidType: bidType, 275 } 276 bidResponse.Bids = append(bidResponse.Bids, b) 277 } 278 } 279 return bidResponse, nil 280 }