github.com/prebid/prebid-server@v0.275.0/adapters/improvedigital/improvedigital.go (about) 1 package improvedigital 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "strconv" 8 "strings" 9 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 const ( 18 buyingTypeRTB = "rtb" 19 isRewardedInventory = "is_rewarded_inventory" 20 stateRewardedInventoryEnable = "1" 21 consentProvidersSettingsInputKey = "ConsentedProvidersSettings" 22 consentProvidersSettingsOutKey = "consented_providers_settings" 23 consentedProvidersKey = "consented_providers" 24 publisherEndpointParam = "{PublisherId}" 25 ) 26 27 type ImprovedigitalAdapter struct { 28 endpoint string 29 } 30 31 // BidExt represents Improved Digital bid extension with line item ID and buying type values 32 type BidExt struct { 33 Improvedigital struct { 34 LineItemID int `json:"line_item_id"` 35 BuyingType string `json:"buying_type"` 36 } 37 } 38 39 // ImpExtBidder represents Improved Digital bid extension with Publisher ID 40 type ImpExtBidder struct { 41 Bidder struct { 42 PublisherID int `json:"publisherId"` 43 } 44 } 45 46 // MakeRequests makes the HTTP requests which should be made to fetch bids. 47 func (a *ImprovedigitalAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { 48 numRequests := len(request.Imp) 49 errors := make([]error, 0) 50 adapterRequests := make([]*adapters.RequestData, 0, numRequests) 51 52 // Split multi-imp request into multiple ad server requests. SRA is currently not recommended. 53 for i := 0; i < numRequests; i++ { 54 if adapterReq, err := a.makeRequest(*request, request.Imp[i]); err == nil { 55 adapterRequests = append(adapterRequests, adapterReq) 56 } else { 57 errors = append(errors, err) 58 } 59 } 60 61 return adapterRequests, errors 62 } 63 64 func (a *ImprovedigitalAdapter) makeRequest(request openrtb2.BidRequest, imp openrtb2.Imp) (*adapters.RequestData, error) { 65 // Handle Rewarded Inventory 66 impExt, err := getImpExtWithRewardedInventory(imp) 67 if err != nil { 68 return nil, err 69 } 70 if impExt != nil { 71 imp.Ext = impExt 72 } 73 74 request.Imp = []openrtb2.Imp{imp} 75 76 userExtAddtlConsent, err := a.getAdditionalConsentProvidersUserExt(request) 77 if err != nil { 78 return nil, err 79 } 80 81 if len(userExtAddtlConsent) > 0 { 82 userCopy := *request.User 83 userCopy.Ext = userExtAddtlConsent 84 request.User = &userCopy 85 } 86 87 reqJSON, err := json.Marshal(request) 88 if err != nil { 89 return nil, err 90 } 91 92 headers := http.Header{} 93 headers.Add("Content-Type", "application/json;charset=utf-8") 94 95 return &adapters.RequestData{ 96 Method: "POST", 97 Uri: a.buildEndpointURL(imp), 98 Body: reqJSON, 99 Headers: headers, 100 }, nil 101 } 102 103 // MakeBids unpacks the server's response into Bids. 104 func (a *ImprovedigitalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { 105 if response.StatusCode == http.StatusNoContent { 106 return nil, nil 107 } 108 109 if response.StatusCode == http.StatusBadRequest { 110 return nil, []error{&errortypes.BadInput{ 111 Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), 112 }} 113 } 114 115 if response.StatusCode != http.StatusOK { 116 return nil, []error{&errortypes.BadServerResponse{ 117 Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), 118 }} 119 } 120 121 var bidResp openrtb2.BidResponse 122 if err := json.Unmarshal(response.Body, &bidResp); err != nil { 123 return nil, []error{err} 124 } 125 126 if len(bidResp.SeatBid) == 0 { 127 return nil, nil 128 } 129 130 if len(bidResp.SeatBid) > 1 { 131 return nil, []error{&errortypes.BadServerResponse{ 132 Message: fmt.Sprintf("Unexpected SeatBid! Must be only one but have: %d", len(bidResp.SeatBid)), 133 }} 134 } 135 136 seatBid := bidResp.SeatBid[0] 137 138 if len(seatBid.Bid) == 0 { 139 return nil, nil 140 } 141 142 bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(seatBid.Bid)) 143 bidResponse.Currency = bidResp.Cur 144 145 for i := range seatBid.Bid { 146 bid := seatBid.Bid[i] 147 148 bidType, err := getMediaTypeForImp(bid.ImpID, internalRequest.Imp) 149 if err != nil { 150 return nil, []error{err} 151 } 152 153 if bid.Ext != nil { 154 var bidExt BidExt 155 err = json.Unmarshal(bid.Ext, &bidExt) 156 if err != nil { 157 return nil, []error{err} 158 } 159 160 bidExtImprovedigital := bidExt.Improvedigital 161 if bidExtImprovedigital.LineItemID != 0 && bidExtImprovedigital.BuyingType != "" && bidExtImprovedigital.BuyingType != buyingTypeRTB { 162 bid.DealID = strconv.Itoa(bidExtImprovedigital.LineItemID) 163 } 164 } 165 166 bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ 167 Bid: &bid, 168 BidType: bidType, 169 }) 170 } 171 return bidResponse, nil 172 173 } 174 175 // Builder builds a new instance of the Improvedigital adapter for the given bidder with the given config. 176 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 177 bidder := &ImprovedigitalAdapter{ 178 endpoint: config.Endpoint, 179 } 180 return bidder, nil 181 } 182 183 func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { 184 for _, imp := range imps { 185 if imp.ID == impID { 186 if imp.Banner != nil { 187 return openrtb_ext.BidTypeBanner, nil 188 } 189 190 if imp.Video != nil { 191 return openrtb_ext.BidTypeVideo, nil 192 } 193 194 if imp.Native != nil { 195 return openrtb_ext.BidTypeNative, nil 196 } 197 198 return "", &errortypes.BadServerResponse{ 199 Message: fmt.Sprintf("Unknown impression type for ID: \"%s\"", impID), 200 } 201 } 202 } 203 204 // This shouldnt happen. Lets handle it just incase by returning an error. 205 return "", &errortypes.BadServerResponse{ 206 Message: fmt.Sprintf("Failed to find impression for ID: \"%s\"", impID), 207 } 208 } 209 210 // This method responsible to clone request and convert additional consent providers string to array when additional consent provider found 211 func (a *ImprovedigitalAdapter) getAdditionalConsentProvidersUserExt(request openrtb2.BidRequest) ([]byte, error) { 212 var cpStr string 213 214 // If user/user.ext not defined, no need to parse additional consent 215 if request.User == nil || request.User.Ext == nil { 216 return nil, nil 217 } 218 219 // Start validating additional consent 220 // Check key exist user.ext.ConsentedProvidersSettings 221 var userExtMap = make(map[string]json.RawMessage) 222 if err := json.Unmarshal(request.User.Ext, &userExtMap); err != nil { 223 return nil, err 224 } 225 226 cpsMapValue, cpsJSONFound := userExtMap[consentProvidersSettingsInputKey] 227 if !cpsJSONFound { 228 return nil, nil 229 } 230 231 // Check key exist user.ext.ConsentedProvidersSettings.consented_providers 232 var cpMap = make(map[string]json.RawMessage) 233 if err := json.Unmarshal(cpsMapValue, &cpMap); err != nil { 234 return nil, err 235 } 236 237 cpMapValue, cpJSONFound := cpMap[consentedProvidersKey] 238 if !cpJSONFound { 239 return nil, nil 240 } 241 // End validating additional consent 242 243 // Check if string contain ~, then substring after ~ to end of string 244 consentStr := string(cpMapValue) 245 var tildaPosition int 246 if tildaPosition = strings.Index(consentStr, "~"); tildaPosition == -1 { 247 return nil, nil 248 } 249 cpStr = consentStr[tildaPosition+1 : len(consentStr)-1] 250 251 // Prepare consent providers string 252 cpStr = fmt.Sprintf("[%s]", strings.Replace(cpStr, ".", ",", -1)) 253 cpMap[consentedProvidersKey] = json.RawMessage(cpStr) 254 255 cpJSON, err := json.Marshal(cpMap) 256 if err != nil { 257 return nil, err 258 } 259 userExtMap[consentProvidersSettingsOutKey] = cpJSON 260 261 extJson, err := json.Marshal(userExtMap) 262 if err != nil { 263 return nil, err 264 } 265 266 return extJson, nil 267 } 268 269 func getImpExtWithRewardedInventory(imp openrtb2.Imp) ([]byte, error) { 270 var ext = make(map[string]json.RawMessage) 271 if err := json.Unmarshal(imp.Ext, &ext); err != nil { 272 return nil, err 273 } 274 275 prebidJSONValue, prebidJSONFound := ext["prebid"] 276 if !prebidJSONFound { 277 return nil, nil 278 } 279 280 var prebidMap = make(map[string]json.RawMessage) 281 if err := json.Unmarshal(prebidJSONValue, &prebidMap); err != nil { 282 return nil, err 283 } 284 285 if rewardedInventory, foundRewardedInventory := prebidMap[isRewardedInventory]; foundRewardedInventory && string(rewardedInventory) == stateRewardedInventoryEnable { 286 ext[isRewardedInventory] = json.RawMessage(`true`) 287 impExt, err := json.Marshal(ext) 288 if err != nil { 289 return nil, err 290 } 291 292 return impExt, nil 293 } 294 295 return nil, nil 296 } 297 298 func (a *ImprovedigitalAdapter) buildEndpointURL(imp openrtb2.Imp) string { 299 publisherEndpoint := "" 300 var impBidder ImpExtBidder 301 302 err := json.Unmarshal(imp.Ext, &impBidder) 303 if err == nil && impBidder.Bidder.PublisherID != 0 { 304 publisherEndpoint = strconv.Itoa(impBidder.Bidder.PublisherID) + "/" 305 } 306 307 return strings.Replace(a.endpoint, publisherEndpointParam, publisherEndpoint, -1) 308 }