github.com/prebid/prebid-server/v2@v2.18.0/adapters/rubicon/rubicon.go (about) 1 package rubicon 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "net/url" 8 "strconv" 9 "strings" 10 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 "github.com/prebid/prebid-server/v2/util/maputil" 16 17 "github.com/buger/jsonparser" 18 "github.com/prebid/openrtb/v20/adcom1" 19 "github.com/prebid/openrtb/v20/openrtb2" 20 ) 21 22 const badvLimitSize = 50 23 24 var bannerExtContent = []byte(`{"rp":{"mime":"text/html"}}`) 25 26 type RubiconAdapter struct { 27 URI string 28 XAPIUsername string 29 XAPIPassword string 30 } 31 32 type rubiconContext struct { 33 Data json.RawMessage `json:"data"` 34 } 35 36 type rubiconData struct { 37 AdServer rubiconAdServer `json:"adserver"` 38 PbAdSlot string `json:"pbadslot"` 39 } 40 41 type rubiconAdServer struct { 42 Name string `json:"name"` 43 AdSlot string `json:"adslot"` 44 } 45 46 type rubiconExtImpBidder struct { 47 Prebid *openrtb_ext.ExtImpPrebid `json:"prebid"` 48 Bidder openrtb_ext.ExtImpRubicon `json:"bidder"` 49 Gpid string `json:"gpid"` 50 Skadn json.RawMessage `json:"skadn,omitempty"` 51 Data json.RawMessage `json:"data"` 52 Context rubiconContext `json:"context"` 53 } 54 55 type bidRequestExt struct { 56 Prebid bidRequestExtPrebid `json:"prebid"` 57 } 58 59 type bidRequestExtPrebid struct { 60 Bidders bidRequestExtPrebidBidders `json:"bidders"` 61 } 62 63 type bidRequestExtPrebidBidders struct { 64 Rubicon prebidBiddersRubicon `json:"rubicon,omitempty"` 65 } 66 67 type prebidBiddersRubicon struct { 68 Debug prebidBiddersRubiconDebug `json:"debug,omitempty"` 69 } 70 71 type prebidBiddersRubiconDebug struct { 72 CpmOverride float64 `json:"cpmoverride,omitempty"` 73 } 74 75 type rubiconImpExtRPTrack struct { 76 Mint string `json:"mint"` 77 MintVersion string `json:"mint_version"` 78 } 79 80 type rubiconImpExt struct { 81 RP rubiconImpExtRP `json:"rp,omitempty"` 82 GPID string `json:"gpid,omitempty"` 83 Skadn json.RawMessage `json:"skadn,omitempty"` 84 } 85 86 type rubiconImpExtRP struct { 87 ZoneID int `json:"zone_id"` 88 Target json.RawMessage `json:"target,omitempty"` 89 Track rubiconImpExtRPTrack `json:"track"` 90 } 91 92 type rubiconUserExtRP struct { 93 Target json.RawMessage `json:"target,omitempty"` 94 } 95 96 type rubiconDataExt struct { 97 SegTax int `json:"segtax"` 98 } 99 100 type rubiconUserExt struct { 101 Eids []openrtb2.EID `json:"eids,omitempty"` 102 RP rubiconUserExtRP `json:"rp"` 103 Data json.RawMessage `json:"data,omitempty"` 104 Consent string `json:"consent,omitempty"` 105 } 106 107 type rubiconSiteExtRP struct { 108 SiteID int `json:"site_id"` 109 Target json.RawMessage `json:"target,omitempty"` 110 } 111 112 type rubiconSiteExt struct { 113 RP rubiconSiteExtRP `json:"rp"` 114 } 115 116 type rubiconPubExtRP struct { 117 AccountID int `json:"account_id"` 118 } 119 120 type rubiconPubExt struct { 121 RP rubiconPubExtRP `json:"rp"` 122 } 123 124 type rubiconBannerExtRP struct { 125 MIME string `json:"mime"` 126 } 127 128 type rubiconBannerExt struct { 129 RP rubiconBannerExtRP `json:"rp"` 130 } 131 132 // ***** Video Extension ***** 133 type rubiconVideoExt struct { 134 Skip int `json:"skip,omitempty"` 135 SkipDelay int `json:"skipdelay,omitempty"` 136 VideoType string `json:"videotype,omitempty"` 137 RP rubiconVideoExtRP `json:"rp"` 138 } 139 140 type rubiconVideoExtRP struct { 141 SizeID int `json:"size_id,omitempty"` 142 } 143 144 type rubiconDeviceExtRP struct { 145 PixelRatio float64 `json:"pixelratio"` 146 } 147 148 type rubiconDeviceExt struct { 149 RP rubiconDeviceExtRP `json:"rp"` 150 } 151 152 type rubiconBidResponse struct { 153 openrtb2.BidResponse 154 SeatBid []rubiconSeatBid `json:"seatbid,omitempty"` 155 } 156 157 type rubiconSeatBid struct { 158 openrtb2.SeatBid 159 Buyer string `json:"buyer,omitempty"` 160 Bid []rubiconBid `json:"bid"` 161 } 162 163 type rubiconBid struct { 164 openrtb2.Bid 165 AdmNative json.RawMessage `json:"adm_native,omitempty"` 166 } 167 168 type extPrebid struct { 169 Prebid *openrtb_ext.ExtBidPrebid `json:"prebid,omitempty"` 170 Bidder json.RawMessage `json:"bidder,omitempty"` 171 } 172 173 // defines the contract for bidrequest.user.ext.eids[i].ext 174 type rubiconUserExtEidExt struct { 175 Segments []string `json:"segments,omitempty"` 176 } 177 178 type mappedRubiconUidsParam struct { 179 segments []string 180 liverampIdl string 181 } 182 183 func resolveVideoSizeId(placement adcom1.VideoPlacementSubtype, instl int8, impId string) (sizeID int, err error) { 184 if placement != 0 { 185 if placement == 1 { 186 return 201, nil 187 } 188 if placement == 3 { 189 return 203, nil 190 } 191 } 192 193 if instl == 1 { 194 return 202, nil 195 } 196 return 0, &errortypes.BadInput{ 197 Message: fmt.Sprintf("video.size_id can not be resolved in impression with id : %s", impId), 198 } 199 } 200 201 func appendTrackerToUrl(uri string, tracker string) (res string) { 202 // Append integration method. Adapter init happens once 203 urlObject, err := url.Parse(uri) 204 // No other exception throwing mechanism in this stack, so ignoring parse errors. 205 if err == nil { 206 values := urlObject.Query() 207 values.Add("tk_xint", tracker) 208 urlObject.RawQuery = values.Encode() 209 res = urlObject.String() 210 } else { 211 res = uri 212 } 213 return 214 } 215 216 // Builder builds a new instance of the Rubicon adapter for the given bidder with the given config. 217 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 218 uri := appendTrackerToUrl(config.Endpoint, config.XAPI.Tracker) 219 220 bidder := &RubiconAdapter{ 221 URI: uri, 222 XAPIUsername: config.XAPI.Username, 223 XAPIPassword: config.XAPI.Password, 224 } 225 return bidder, nil 226 } 227 228 func updateRequestTo26(r *openrtb2.BidRequest) error { 229 if r.Regs != nil { 230 regsCopy := *r.Regs 231 r.Regs = ®sCopy 232 } 233 234 if r.Source != nil { 235 sourceCopy := *r.Source 236 r.Source = &sourceCopy 237 } 238 239 if r.User != nil { 240 userCopy := *r.User 241 r.User = &userCopy 242 } 243 244 requestWrapper := &openrtb_ext.RequestWrapper{BidRequest: r} 245 246 if err := openrtb_ext.ConvertUpTo26(requestWrapper); err != nil { 247 return err 248 } 249 250 return requestWrapper.RebuildRequest() 251 } 252 253 func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { 254 255 err := updateRequestTo26(request) 256 257 if err != nil { 258 return nil, []error{err} 259 } 260 261 numRequests := len(request.Imp) 262 requestData := make([]*adapters.RequestData, 0, numRequests) 263 headers := http.Header{} 264 headers.Add("Content-Type", "application/json;charset=utf-8") 265 headers.Add("Accept", "application/json") 266 headers.Add("User-Agent", "prebid-server/1.0") 267 268 impsToExtNotGrouped, errs := createImpsToExtMap(request.Imp) 269 impsToExtMap := prepareImpsToExtMap(impsToExtNotGrouped) 270 271 rubiconRequest := *request 272 for imp, bidderExt := range impsToExtMap { 273 rubiconExt := bidderExt.Bidder 274 target, err := updateImpRpTargetWithFpdAttributes(bidderExt, rubiconExt, *imp, request.Site, request.App) 275 if err != nil { 276 errs = append(errs, err) 277 continue 278 } 279 280 siteId, err := rubiconExt.SiteId.Int64() 281 if err != nil { 282 errs = append(errs, err) 283 continue 284 } 285 286 zoneId, err := rubiconExt.ZoneId.Int64() 287 if err != nil { 288 errs = append(errs, err) 289 continue 290 } 291 292 impExt := rubiconImpExt{ 293 RP: rubiconImpExtRP{ 294 ZoneID: int(zoneId), 295 Target: target, 296 Track: rubiconImpExtRPTrack{Mint: "", MintVersion: ""}, 297 }, 298 GPID: bidderExt.Gpid, 299 Skadn: bidderExt.Skadn, 300 } 301 302 imp.Ext, err = json.Marshal(&impExt) 303 if err != nil { 304 errs = append(errs, err) 305 continue 306 } 307 308 secure := int8(1) 309 imp.Secure = &secure 310 311 resolvedBidFloor, err := resolveBidFloor(imp.BidFloor, imp.BidFloorCur, reqInfo) 312 if err != nil { 313 errs = append(errs, &errortypes.BadInput{ 314 Message: fmt.Sprintf("Unable to convert provided bid floor currency from %s to USD", 315 imp.BidFloorCur), 316 }) 317 continue 318 } 319 320 if resolvedBidFloor > 0 { 321 imp.BidFloorCur = "USD" 322 imp.BidFloor = resolvedBidFloor 323 } 324 325 if request.User != nil { 326 userCopy := *request.User 327 target, err := updateUserRpTargetWithFpdAttributes(rubiconExt.Visitor, userCopy) 328 if err != nil { 329 errs = append(errs, err) 330 continue 331 } 332 333 userExtRP := rubiconUserExt{RP: rubiconUserExtRP{Target: target}} 334 userBuyerUID := userCopy.BuyerUID 335 336 if len(userCopy.EIDs) > 0 { 337 userExtRP.Eids = userCopy.EIDs 338 339 if userBuyerUID == "" { 340 userBuyerUID = extractUserBuyerUID(userExtRP.Eids) 341 } 342 } 343 344 if userCopy.Consent != "" { 345 userExtRP.Consent = userCopy.Consent 346 userCopy.Consent = "" 347 } 348 349 userCopy.Ext, err = json.Marshal(&userExtRP) 350 if err != nil { 351 errs = append(errs, err) 352 continue 353 } 354 userCopy.Geo = nil 355 userCopy.Yob = 0 356 userCopy.Gender = "" 357 userCopy.BuyerUID = userBuyerUID 358 userCopy.EIDs = nil 359 360 rubiconRequest.User = &userCopy 361 } 362 363 if request.Device != nil { 364 deviceCopy := *request.Device 365 deviceExt := rubiconDeviceExt{RP: rubiconDeviceExtRP{PixelRatio: request.Device.PxRatio}} 366 deviceCopy.Ext, err = json.Marshal(&deviceExt) 367 rubiconRequest.Device = &deviceCopy 368 } 369 370 isVideo := isVideo(*imp) 371 impType := openrtb_ext.BidTypeVideo 372 requestNative := make(map[string]interface{}) 373 if isVideo { 374 videoCopy := *imp.Video 375 376 videoSizeId := rubiconExt.Video.VideoSizeID 377 if videoSizeId == 0 { 378 resolvedSizeId, err := resolveVideoSizeId(imp.Video.Placement, imp.Instl, imp.ID) 379 if err != nil { 380 errs = append(errs, err) 381 continue 382 } 383 videoSizeId = resolvedSizeId 384 } 385 386 // if imp.rwdd = 1, set imp.video.ext.videotype = "rewarded" 387 var videoType = "" 388 if imp.Rwdd == 1 { 389 videoType = "rewarded" 390 imp.Rwdd = 0 391 } 392 videoExt := rubiconVideoExt{Skip: rubiconExt.Video.Skip, SkipDelay: rubiconExt.Video.SkipDelay, VideoType: videoType, RP: rubiconVideoExtRP{SizeID: videoSizeId}} 393 videoCopy.Ext, err = json.Marshal(&videoExt) 394 imp.Video = &videoCopy 395 imp.Banner = nil 396 imp.Native = nil 397 } else if imp.Banner != nil { 398 bannerCopy := *imp.Banner 399 if len(bannerCopy.Format) < 1 && (bannerCopy.W == nil || *bannerCopy.W == 0 && bannerCopy.H == nil || *bannerCopy.H == 0) { 400 errs = append(errs, &errortypes.BadInput{ 401 Message: "rubicon imps must have at least one imp.format element", 402 }) 403 continue 404 } 405 bannerCopy.Ext = bannerExtContent 406 if err != nil { 407 errs = append(errs, err) 408 continue 409 } 410 imp.Banner = &bannerCopy 411 imp.Video = nil 412 imp.Native = nil 413 impType = openrtb_ext.BidTypeBanner 414 } else { 415 native, err := resolveNativeObject(imp.Native, requestNative) 416 if err != nil { 417 errs = append(errs, err) 418 continue 419 } 420 imp.Native = native 421 imp.Video = nil 422 impType = openrtb_ext.BidTypeNative 423 } 424 425 accountId, err := rubiconExt.AccountId.Int64() 426 if err != nil { 427 errs = append(errs, err) 428 continue 429 } 430 431 pubExt := rubiconPubExt{RP: rubiconPubExtRP{AccountID: int(accountId)}} 432 433 if request.Site != nil { 434 siteCopy := *request.Site 435 siteExtRP := rubiconSiteExt{RP: rubiconSiteExtRP{SiteID: int(siteId)}} 436 if siteCopy.Content != nil { 437 siteTarget := make(map[string]interface{}) 438 updateExtWithIabAttribute(siteTarget, siteCopy.Content.Data, []int{1, 2, 5, 6}) 439 if len(siteTarget) > 0 { 440 updatedSiteTarget, err := json.Marshal(siteTarget) 441 if err != nil { 442 errs = append(errs, err) 443 continue 444 } 445 siteExtRP.RP.Target = updatedSiteTarget 446 } 447 } 448 449 siteCopy.Ext, err = json.Marshal(&siteExtRP) 450 if err != nil { 451 errs = append(errs, err) 452 continue 453 } 454 455 siteCopy.Publisher = &openrtb2.Publisher{} 456 siteCopy.Publisher.Ext, err = json.Marshal(&pubExt) 457 rubiconRequest.Site = &siteCopy 458 } else { 459 appCopy := *request.App 460 appCopy.Ext, err = json.Marshal(rubiconSiteExt{RP: rubiconSiteExtRP{SiteID: int(siteId)}}) 461 appCopy.Publisher = &openrtb2.Publisher{} 462 appCopy.Publisher.Ext, err = json.Marshal(&pubExt) 463 rubiconRequest.App = &appCopy 464 } 465 466 if request.Source != nil || rubiconExt.PChain != "" { 467 var sourceCopy openrtb2.Source 468 if request.Source != nil { 469 sourceCopy = *request.Source 470 } else { 471 sourceCopy = openrtb2.Source{} 472 } 473 474 if sourceCopy.SChain != nil { 475 var sourceCopyExt openrtb_ext.ExtSource 476 if sourceCopy.Ext != nil { 477 if err = json.Unmarshal(sourceCopy.Ext, &sourceCopyExt); err != nil { 478 errs = append(errs, &errortypes.BadInput{Message: err.Error()}) 479 continue 480 } 481 } else { 482 sourceCopyExt = openrtb_ext.ExtSource{} 483 } 484 485 sourceCopyExt.SChain = sourceCopy.SChain 486 sourceCopy.SChain = nil 487 488 sourceCopy.Ext, err = json.Marshal(&sourceCopyExt) 489 if err != nil { 490 errs = append(errs, err) 491 continue 492 } 493 } 494 495 if rubiconExt.PChain != "" { 496 sourceCopy.PChain = rubiconExt.PChain 497 } 498 499 rubiconRequest.Source = &sourceCopy 500 } 501 502 if request.Regs != nil && (request.Regs.GDPR != nil || request.Regs.USPrivacy != "") { 503 regsCopy := *request.Regs 504 505 var regsCopyExt openrtb_ext.ExtRegs 506 if regsCopy.Ext != nil { 507 if err = json.Unmarshal(regsCopy.Ext, ®sCopyExt); err != nil { 508 errs = append(errs, &errortypes.BadInput{Message: err.Error()}) 509 continue 510 } 511 } else { 512 regsCopyExt = openrtb_ext.ExtRegs{} 513 } 514 515 if regsCopy.GDPR != nil { 516 regsCopyExt.GDPR = regsCopy.GDPR 517 } 518 if regsCopy.USPrivacy != "" { 519 regsCopyExt.USPrivacy = regsCopy.USPrivacy 520 } 521 522 regsCopy.Ext, err = json.Marshal(®sCopyExt) 523 if err != nil { 524 errs = append(errs, err) 525 continue 526 } 527 regsCopy.GDPR = nil 528 regsCopy.USPrivacy = "" 529 530 rubiconRequest.Regs = ®sCopy 531 } 532 533 reqBadv := request.BAdv 534 if reqBadv != nil { 535 if len(reqBadv) > badvLimitSize { 536 rubiconRequest.BAdv = reqBadv[:badvLimitSize] 537 } 538 } 539 540 rubiconRequest.Imp = []openrtb2.Imp{*imp} 541 rubiconRequest.Cur = nil 542 rubiconRequest.Ext = nil 543 544 reqJSON, err := json.Marshal(rubiconRequest) 545 if impType == openrtb_ext.BidTypeNative && len(requestNative) > 0 { 546 reqJSON, err = setImpNative(reqJSON, requestNative) 547 } 548 549 if err != nil { 550 errs = append(errs, err) 551 continue 552 } 553 554 reqData := &adapters.RequestData{ 555 Method: "POST", 556 Uri: a.URI, 557 Body: reqJSON, 558 Headers: headers, 559 ImpIDs: openrtb_ext.GetImpIDs(rubiconRequest.Imp), 560 } 561 reqData.SetBasicAuth(a.XAPIUsername, a.XAPIPassword) 562 requestData = append(requestData, reqData) 563 } 564 565 return requestData, errs 566 } 567 568 func createImpsToExtMap(imps []openrtb2.Imp) (map[*openrtb2.Imp]rubiconExtImpBidder, []error) { 569 impsToExtMap := make(map[*openrtb2.Imp]rubiconExtImpBidder) 570 errs := make([]error, 0) 571 var err error 572 for _, imp := range imps { 573 impCopy := imp 574 var bidderExt rubiconExtImpBidder 575 if err = json.Unmarshal(imp.Ext, &bidderExt); err != nil { 576 errs = append(errs, &errortypes.BadInput{ 577 Message: err.Error(), 578 }) 579 continue 580 } 581 impsToExtMap[&impCopy] = bidderExt 582 } 583 584 return impsToExtMap, errs 585 } 586 587 func prepareImpsToExtMap(impsToExtMap map[*openrtb2.Imp]rubiconExtImpBidder) map[*openrtb2.Imp]rubiconExtImpBidder { 588 preparedImpsToExtMap := make(map[*openrtb2.Imp]rubiconExtImpBidder) 589 for imp, bidderExt := range impsToExtMap { 590 if bidderExt.Bidder.BidOnMultiformat == false { //nolint: staticcheck 591 impCopy := imp 592 preparedImpsToExtMap[impCopy] = bidderExt 593 continue 594 } 595 596 splitImps := splitMultiFormatImp(imp) 597 for _, imp := range splitImps { 598 impCopy := imp 599 preparedImpsToExtMap[impCopy] = bidderExt 600 } 601 } 602 603 return preparedImpsToExtMap 604 } 605 606 func splitMultiFormatImp(imp *openrtb2.Imp) []*openrtb2.Imp { 607 splitImps := make([]*openrtb2.Imp, 0) 608 if imp.Banner != nil { 609 impCopy := *imp 610 impCopy.Video = nil 611 impCopy.Native = nil 612 impCopy.Audio = nil 613 splitImps = append(splitImps, &impCopy) 614 } 615 616 if imp.Video != nil { 617 impCopy := *imp 618 impCopy.Banner = nil 619 impCopy.Native = nil 620 impCopy.Audio = nil 621 splitImps = append(splitImps, &impCopy) 622 } 623 624 if imp.Native != nil { 625 impCopy := *imp 626 impCopy.Banner = nil 627 impCopy.Video = nil 628 impCopy.Audio = nil 629 splitImps = append(splitImps, &impCopy) 630 } 631 632 if imp.Audio != nil { 633 impCopy := *imp 634 impCopy.Banner = nil 635 impCopy.Video = nil 636 impCopy.Native = nil 637 splitImps = append(splitImps, &impCopy) 638 } 639 640 return splitImps 641 } 642 643 func resolveBidFloor(bidFloor float64, bidFloorCur string, reqInfo *adapters.ExtraRequestInfo) (float64, error) { 644 if bidFloor > 0 && bidFloorCur != "" && strings.ToUpper(bidFloorCur) != "USD" { 645 return reqInfo.ConvertCurrency(bidFloor, bidFloorCur, "USD") 646 } 647 648 return bidFloor, nil 649 } 650 651 func updateImpRpTargetWithFpdAttributes(extImp rubiconExtImpBidder, extImpRubicon openrtb_ext.ExtImpRubicon, 652 imp openrtb2.Imp, site *openrtb2.Site, app *openrtb2.App) (json.RawMessage, error) { 653 654 existingTarget, _, _, err := jsonparser.Get(imp.Ext, "rp", "target") 655 if isNotKeyPathError(err) { 656 return nil, err 657 } 658 target, err := rawJSONToMap(existingTarget) 659 if err != nil { 660 return nil, err 661 } 662 err = populateFirstPartyDataAttributes(extImpRubicon.Inventory, target) 663 if err != nil { 664 return nil, err 665 } 666 667 if site != nil { 668 siteExtData, _, _, err := jsonparser.Get(site.Ext, "data") 669 if isNotKeyPathError(err) { 670 return nil, err 671 } 672 err = populateFirstPartyDataAttributes(siteExtData, target) 673 if err != nil { 674 return nil, err 675 } 676 if len(site.SectionCat) > 0 { 677 addStringArrayAttribute(site.SectionCat, target, "sectioncat") 678 } 679 if len(site.PageCat) > 0 { 680 addStringArrayAttribute(site.PageCat, target, "pagecat") 681 } 682 if site.Page != "" { 683 addStringAttribute(site.Page, target, "page") 684 } 685 if site.Ref != "" { 686 addStringAttribute(site.Ref, target, "ref") 687 } 688 if site.Search != "" { 689 addStringAttribute(site.Search, target, "search") 690 } 691 } else { 692 appExtData, _, _, err := jsonparser.Get(app.Ext, "data") 693 if isNotKeyPathError(err) { 694 return nil, err 695 } 696 err = populateFirstPartyDataAttributes(appExtData, target) 697 if err != nil { 698 return nil, err 699 } 700 if len(app.SectionCat) > 0 { 701 addStringArrayAttribute(app.SectionCat, target, "sectioncat") 702 } 703 if len(app.PageCat) > 0 { 704 addStringArrayAttribute(app.PageCat, target, "pagecat") 705 } 706 } 707 708 if len(extImp.Context.Data) > 0 { 709 err = populateFirstPartyDataAttributes(extImp.Context.Data, target) 710 } else if len(extImp.Data) > 0 { 711 err = populateFirstPartyDataAttributes(extImp.Data, target) 712 } 713 if isNotKeyPathError(err) { 714 return nil, err 715 } 716 717 var data rubiconData 718 if len(extImp.Data) > 0 { 719 err := json.Unmarshal(extImp.Data, &data) 720 if err != nil { 721 return nil, err 722 } 723 } 724 var contextData rubiconData 725 if len(extImp.Context.Data) > 0 { 726 err := json.Unmarshal(extImp.Context.Data, &contextData) 727 if err != nil { 728 return nil, err 729 } 730 } 731 732 if data.PbAdSlot != "" { 733 target["pbadslot"] = data.PbAdSlot 734 } else { 735 dfpAdUnitCode := extractDfpAdUnitCode(data, contextData) 736 if dfpAdUnitCode != "" { 737 target["dfp_ad_unit_code"] = dfpAdUnitCode 738 } 739 } 740 741 if len(extImpRubicon.Keywords) > 0 { 742 addStringArrayAttribute(extImpRubicon.Keywords, target, "keywords") 743 } 744 updatedTarget, err := json.Marshal(target) 745 if err != nil { 746 return nil, err 747 } 748 return updatedTarget, nil 749 } 750 751 func extractDfpAdUnitCode(data rubiconData, contextData rubiconData) string { 752 if contextData.AdServer.Name == "gam" && contextData.AdServer.AdSlot != "" { 753 return contextData.AdServer.AdSlot 754 } else if data.AdServer.Name == "gam" && data.AdServer.AdSlot != "" { 755 return data.AdServer.AdSlot 756 } 757 758 return "" 759 } 760 761 func isNotKeyPathError(err error) bool { 762 return err != nil && err != jsonparser.KeyPathNotFoundError 763 } 764 765 func addStringAttribute(attribute string, target map[string]interface{}, attributeName string) { 766 target[attributeName] = [1]string{attribute} 767 } 768 769 func addStringArrayAttribute(attribute []string, target map[string]interface{}, attributeName string) { 770 target[attributeName] = attribute 771 } 772 773 func updateUserRpTargetWithFpdAttributes(visitor json.RawMessage, user openrtb2.User) (json.RawMessage, error) { 774 existingTarget, _, _, err := jsonparser.Get(user.Ext, "rp", "target") 775 if isNotKeyPathError(err) { 776 return nil, err 777 } 778 target, err := rawJSONToMap(existingTarget) 779 if err != nil { 780 return nil, err 781 } 782 err = populateFirstPartyDataAttributes(visitor, target) 783 if err != nil { 784 return nil, err 785 } 786 userExtData, _, _, err := jsonparser.Get(user.Ext, "data") 787 if isNotKeyPathError(err) { 788 return nil, err 789 } 790 err = populateFirstPartyDataAttributes(userExtData, target) 791 if err != nil { 792 return nil, err 793 } 794 updateExtWithIabAttribute(target, user.Data, []int{4}) 795 796 updatedTarget, err := json.Marshal(target) 797 if err != nil { 798 return nil, err 799 } 800 return updatedTarget, nil 801 } 802 803 func updateExtWithIabAttribute(target map[string]interface{}, data []openrtb2.Data, segTaxes []int) { 804 var segmentIdsToCopy = getSegmentIdsToCopy(data, segTaxes) 805 if len(segmentIdsToCopy) == 0 { 806 return 807 } 808 809 target["iab"] = segmentIdsToCopy 810 } 811 812 func populateFirstPartyDataAttributes(source json.RawMessage, target map[string]interface{}) error { 813 sourceAsMap, err := rawJSONToMap(source) 814 if err != nil { 815 return err 816 } 817 818 for key, val := range sourceAsMap { 819 switch typedValue := val.(type) { 820 case string: 821 target[key] = [1]string{typedValue} 822 case float64: 823 if typedValue == float64(int(typedValue)) { 824 target[key] = [1]string{strconv.Itoa(int(typedValue))} 825 } 826 case bool: 827 target[key] = [1]string{strconv.FormatBool(typedValue)} 828 case []interface{}: 829 if isStringArray(typedValue) { 830 target[key] = typedValue 831 } 832 if isBoolArray(typedValue) { 833 target[key] = convertToStringArray(typedValue) 834 } 835 } 836 } 837 return nil 838 } 839 840 func isStringArray(array []interface{}) bool { 841 for _, val := range array { 842 if _, ok := val.(string); !ok { 843 return false 844 } 845 } 846 847 return true 848 } 849 850 func isBoolArray(array []interface{}) bool { 851 for _, val := range array { 852 if _, ok := val.(bool); !ok { 853 return false 854 } 855 } 856 857 return true 858 } 859 860 func convertToStringArray(arr []interface{}) []string { 861 var stringArray []string 862 for _, val := range arr { 863 if boolVal, ok := val.(bool); ok { 864 stringArray = append(stringArray, strconv.FormatBool(boolVal)) 865 } 866 } 867 868 return stringArray 869 } 870 871 func rawJSONToMap(message json.RawMessage) (map[string]interface{}, error) { 872 if message == nil { 873 return make(map[string]interface{}), nil 874 } 875 876 return mapFromRawJSON(message) 877 } 878 879 func mapFromRawJSON(message json.RawMessage) (map[string]interface{}, error) { 880 targetAsMap := make(map[string]interface{}) 881 err := json.Unmarshal(message, &targetAsMap) 882 if err != nil { 883 return nil, err 884 } 885 return targetAsMap, nil 886 } 887 888 func getSegmentIdsToCopy(data []openrtb2.Data, segTaxValues []int) []string { 889 var segmentIdsToCopy = make([]string, 0, len(data)) 890 891 for _, dataRecord := range data { 892 if dataRecord.Ext != nil { 893 var dataExtObject rubiconDataExt 894 err := json.Unmarshal(dataRecord.Ext, &dataExtObject) 895 if err != nil { 896 continue 897 } 898 if contains(segTaxValues, dataExtObject.SegTax) { 899 for _, segment := range dataRecord.Segment { 900 segmentIdsToCopy = append(segmentIdsToCopy, segment.ID) 901 } 902 } 903 } 904 } 905 return segmentIdsToCopy 906 } 907 908 func contains(s []int, e int) bool { 909 for _, a := range s { 910 if a == e { 911 return true 912 } 913 } 914 return false 915 } 916 917 func extractUserBuyerUID(eids []openrtb2.EID) string { 918 for _, eid := range eids { 919 if eid.Source != "rubiconproject.com" { 920 continue 921 } 922 923 for _, uid := range eid.UIDs { 924 return uid.ID 925 } 926 } 927 928 return "" 929 } 930 931 func isVideo(imp openrtb2.Imp) bool { 932 video := imp.Video 933 if video != nil { 934 // Do any other media types exist? Or check required video fields. 935 return imp.Banner == nil || isFullyPopulatedVideo(video) 936 } 937 return false 938 } 939 940 func isFullyPopulatedVideo(video *openrtb2.Video) bool { 941 // These are just recommended video fields for XAPI 942 return video.MIMEs != nil && video.Protocols != nil && video.MaxDuration != 0 && video.Linearity != 0 943 } 944 945 func resolveNativeObject(native *openrtb2.Native, target map[string]interface{}) (*openrtb2.Native, error) { 946 if native == nil { 947 return nil, fmt.Errorf("Native object is not present for request") 948 } 949 ver := native.Ver 950 if ver == "1.0" || ver == "1.1" { 951 return native, nil 952 } 953 954 err := json.Unmarshal([]byte(native.Request), &target) 955 if err != nil { 956 return nil, err 957 } 958 959 if _, ok := target["eventtrackers"].([]interface{}); !ok { 960 return nil, fmt.Errorf("Eventtrackers are not present or not of array type") 961 } 962 963 context := target["context"] 964 if context != nil { 965 if _, ok := context.(float64); !ok { 966 return nil, fmt.Errorf("Context is not of int type") 967 } 968 } 969 970 if _, ok := target["plcmttype"].(float64); !ok { 971 return nil, fmt.Errorf("Plcmttype is not present or not of int type") 972 } 973 974 return native, nil 975 } 976 977 func setImpNative(jsonData []byte, requestNative map[string]interface{}) ([]byte, error) { 978 var jsonMap map[string]interface{} 979 if err := json.Unmarshal(jsonData, &jsonMap); err != nil { 980 return jsonData, err 981 } 982 983 var impMap map[string]interface{} 984 if impSlice, ok := maputil.ReadEmbeddedSlice(jsonMap, "imp"); !ok { 985 return jsonData, fmt.Errorf("unable to find imp in json data") 986 } else if len(impSlice) == 0 { 987 return jsonData, fmt.Errorf("unable to find imp[0] in json data") 988 } else if impMap, ok = impSlice[0].(map[string]interface{}); !ok { 989 return jsonData, fmt.Errorf("unexpected type for imp[0] found in json data") 990 } 991 992 nativeMap, ok := maputil.ReadEmbeddedMap(impMap, "native") 993 if !ok { 994 return jsonData, fmt.Errorf("unable to find imp[0].native in json data") 995 } 996 997 nativeMap["request_native"] = requestNative 998 999 if jsonReEncoded, err := json.Marshal(jsonMap); err == nil { 1000 return jsonReEncoded, nil 1001 } else { 1002 return nil, fmt.Errorf("unable to encode json data (%v)", err) 1003 } 1004 } 1005 1006 func (a *RubiconAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { 1007 if response.StatusCode == http.StatusNoContent { 1008 return nil, nil 1009 } 1010 1011 if response.StatusCode == http.StatusBadRequest { 1012 return nil, []error{&errortypes.BadInput{ 1013 Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), 1014 }} 1015 } 1016 1017 if response.StatusCode != http.StatusOK { 1018 return nil, []error{&errortypes.BadServerResponse{ 1019 Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), 1020 }} 1021 } 1022 1023 var bidResp rubiconBidResponse 1024 if err := json.Unmarshal(response.Body, &bidResp); err != nil { 1025 return nil, []error{&errortypes.BadServerResponse{ 1026 Message: err.Error(), 1027 }} 1028 } 1029 1030 var bidReq openrtb2.BidRequest 1031 if err := json.Unmarshal(externalRequest.Body, &bidReq); err != nil { 1032 return nil, []error{err} 1033 } 1034 1035 bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) 1036 1037 bidType := openrtb_ext.BidTypeNative 1038 1039 isVideo := isVideo(bidReq.Imp[0]) 1040 if isVideo { 1041 bidType = openrtb_ext.BidTypeVideo 1042 } else if bidReq.Imp[0].Banner != nil { 1043 bidType = openrtb_ext.BidTypeBanner 1044 } 1045 1046 impToCpmOverride := mapImpIdToCpmOverride(internalRequest.Imp) 1047 cmpOverride := cmpOverrideFromBidRequest(internalRequest) 1048 1049 for _, sb := range bidResp.SeatBid { 1050 buyer, err := strconv.Atoi(sb.Buyer) 1051 if err != nil { 1052 buyer = 0 1053 } 1054 for i := 0; i < len(sb.Bid); i++ { 1055 bid := sb.Bid[i] 1056 1057 updatedBidExt := updateBidExtWithMetaNetworkId(bid, buyer) 1058 if updatedBidExt != nil { 1059 bid.Ext = updatedBidExt 1060 } 1061 bidCmpOverride, ok := impToCpmOverride[bid.ImpID] 1062 if !ok || bidCmpOverride == 0 { 1063 bidCmpOverride = cmpOverride 1064 } 1065 1066 if bidCmpOverride > 0 { 1067 bid.Price = bidCmpOverride 1068 } 1069 1070 if bid.Price != 0 { 1071 // Since Rubicon XAPI returns only one bid per response 1072 // copy response.bidid to openrtb_response.seatbid.bid.bidid 1073 if bid.ID == "0" { 1074 bid.ID = bidResp.BidID 1075 } 1076 1077 resolvedAdm := resolveAdm(bid) 1078 if len(resolvedAdm) > 0 { 1079 bid.AdM = resolvedAdm 1080 } 1081 1082 var ortbBid openrtb2.Bid // `targetStruct` can be anything of your choice 1083 1084 rubiconBidAsBytes, _ := json.Marshal(bid) 1085 if len(rubiconBidAsBytes) > 0 { 1086 err = json.Unmarshal(rubiconBidAsBytes, &ortbBid) 1087 if err != nil { 1088 return nil, []error{err} 1089 } 1090 } 1091 1092 bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ 1093 Bid: &ortbBid, 1094 BidType: bidType, 1095 }) 1096 } 1097 } 1098 } 1099 if bidResp.Cur != "" { 1100 bidResponse.Currency = bidResp.Cur 1101 } 1102 1103 return bidResponse, nil 1104 } 1105 1106 func mapImpIdToCpmOverride(imps []openrtb2.Imp) map[string]float64 { 1107 impIdToCmpOverride := make(map[string]float64) 1108 for _, imp := range imps { 1109 var bidderExt adapters.ExtImpBidder 1110 if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { 1111 continue 1112 } 1113 1114 var rubiconExt openrtb_ext.ExtImpRubicon 1115 if err := json.Unmarshal(bidderExt.Bidder, &rubiconExt); err != nil { 1116 continue 1117 } 1118 1119 impIdToCmpOverride[imp.ID] = rubiconExt.Debug.CpmOverride 1120 } 1121 return impIdToCmpOverride 1122 } 1123 1124 func resolveAdm(bid rubiconBid) string { 1125 var bidAdm = bid.AdM 1126 if len(bidAdm) > 0 { 1127 return bidAdm 1128 } 1129 1130 admObject := bid.AdmNative 1131 admObjectAsBytes, err := json.Marshal(&admObject) 1132 if err != nil { 1133 return "" 1134 } 1135 1136 return string(admObjectAsBytes) 1137 } 1138 1139 func cmpOverrideFromBidRequest(bidRequest *openrtb2.BidRequest) float64 { 1140 var bidRequestExt bidRequestExt 1141 if err := json.Unmarshal(bidRequest.Ext, &bidRequestExt); err != nil { 1142 return 0 1143 } 1144 1145 return bidRequestExt.Prebid.Bidders.Rubicon.Debug.CpmOverride 1146 } 1147 1148 func updateBidExtWithMetaNetworkId(bid rubiconBid, buyer int) json.RawMessage { 1149 if buyer <= 0 { 1150 return nil 1151 } 1152 var bidExt *extPrebid 1153 if bid.Ext != nil { 1154 if err := json.Unmarshal(bid.Ext, &bidExt); err != nil { 1155 return nil 1156 } 1157 } 1158 1159 if bidExt != nil { 1160 if bidExt.Prebid != nil { 1161 if bidExt.Prebid.Meta != nil { 1162 bidExt.Prebid.Meta.NetworkID = buyer 1163 } else { 1164 bidExt.Prebid.Meta = &openrtb_ext.ExtBidPrebidMeta{NetworkID: buyer} 1165 } 1166 } else { 1167 bidExt.Prebid = &openrtb_ext.ExtBidPrebid{Meta: &openrtb_ext.ExtBidPrebidMeta{NetworkID: buyer}} 1168 } 1169 } else { 1170 bidExt = &extPrebid{Prebid: &openrtb_ext.ExtBidPrebid{Meta: &openrtb_ext.ExtBidPrebidMeta{NetworkID: buyer}}} 1171 } 1172 1173 marshalledExt, err := json.Marshal(&bidExt) 1174 if err == nil { 1175 return marshalledExt 1176 } 1177 return nil 1178 }