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 = &regsCopy
   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, &regsCopyExt); 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(&regsCopyExt)
   523  			if err != nil {
   524  				errs = append(errs, err)
   525  				continue
   526  			}
   527  			regsCopy.GDPR = nil
   528  			regsCopy.USPrivacy = ""
   529  
   530  			rubiconRequest.Regs = &regsCopy
   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  }