github.com/prebid/prebid-server@v0.275.0/openrtb_ext/request.go (about)

     1  package openrtb_ext
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  
     7  	"github.com/prebid/openrtb/v19/openrtb2"
     8  	"github.com/prebid/prebid-server/util/maputil"
     9  	"github.com/prebid/prebid-server/util/ptrutil"
    10  	"github.com/prebid/prebid-server/util/sliceutil"
    11  )
    12  
    13  // FirstPartyDataExtKey defines a field name within request.ext and request.imp.ext reserved for first party data.
    14  const FirstPartyDataExtKey = "data"
    15  
    16  // FirstPartyDataContextExtKey defines a field name within request.ext and request.imp.ext reserved for first party data.
    17  const FirstPartyDataContextExtKey = "context"
    18  
    19  // SKAdNExtKey defines the field name within request.ext reserved for Apple's SKAdNetwork.
    20  const SKAdNExtKey = "skadn"
    21  
    22  // GPIDKey defines the field name within request.ext reserved for the Global Placement ID (GPID),
    23  const GPIDKey = "gpid"
    24  
    25  // TIDKey reserved for Per-Impression Transactions IDs for Multi-Impression Bid Requests.
    26  const TIDKey = "tid"
    27  
    28  // AuctionEnvironmentKey is the json key under imp[].ext for ExtImp.AuctionEnvironment
    29  const AuctionEnvironmentKey = string(BidderReservedAE)
    30  
    31  // NativeExchangeSpecificLowerBound defines the lower threshold of exchange specific types for native ads. There is no upper bound.
    32  const NativeExchangeSpecificLowerBound = 500
    33  
    34  const MaxDecimalFigures int = 15
    35  
    36  // ExtRequest defines the contract for bidrequest.ext
    37  type ExtRequest struct {
    38  	Prebid ExtRequestPrebid      `json:"prebid"`
    39  	SChain *openrtb2.SupplyChain `json:"schain,omitempty"`
    40  }
    41  
    42  // ExtRequestPrebid defines the contract for bidrequest.ext.prebid
    43  type ExtRequestPrebid struct {
    44  	AdServerTargeting    []AdServerTarget                `json:"adservertargeting,omitempty"`
    45  	Aliases              map[string]string               `json:"aliases,omitempty"`
    46  	AliasGVLIDs          map[string]uint16               `json:"aliasgvlids,omitempty"`
    47  	BidAdjustmentFactors map[string]float64              `json:"bidadjustmentfactors,omitempty"`
    48  	BidAdjustments       *ExtRequestPrebidBidAdjustments `json:"bidadjustments,omitempty"`
    49  	BidderConfigs        []BidderConfig                  `json:"bidderconfig,omitempty"`
    50  	BidderParams         json.RawMessage                 `json:"bidderparams,omitempty"`
    51  	Cache                *ExtRequestPrebidCache          `json:"cache,omitempty"`
    52  	Channel              *ExtRequestPrebidChannel        `json:"channel,omitempty"`
    53  	CurrencyConversions  *ExtRequestCurrency             `json:"currency,omitempty"`
    54  	Data                 *ExtRequestPrebidData           `json:"data,omitempty"`
    55  	Debug                bool                            `json:"debug,omitempty"`
    56  	Events               json.RawMessage                 `json:"events,omitempty"`
    57  	Experiment           *Experiment                     `json:"experiment,omitempty"`
    58  	Floors               *PriceFloorRules                `json:"floors,omitempty"`
    59  	Integration          string                          `json:"integration,omitempty"`
    60  	MultiBid             []*ExtMultiBid                  `json:"multibid,omitempty"`
    61  	MultiBidMap          map[string]ExtMultiBid          `json:"-"`
    62  	Passthrough          json.RawMessage                 `json:"passthrough,omitempty"`
    63  	SChains              []*ExtRequestPrebidSChain       `json:"schains,omitempty"`
    64  	Sdk                  *ExtRequestSdk                  `json:"sdk,omitempty"`
    65  	Server               *ExtRequestPrebidServer         `json:"server,omitempty"`
    66  	StoredRequest        *ExtStoredRequest               `json:"storedrequest,omitempty"`
    67  	SupportDeals         bool                            `json:"supportdeals,omitempty"`
    68  	Targeting            *ExtRequestTargeting            `json:"targeting,omitempty"`
    69  
    70  	//AlternateBidderCodes is populated with host's AlternateBidderCodes config if not defined in request
    71  	AlternateBidderCodes *ExtAlternateBidderCodes `json:"alternatebiddercodes,omitempty"`
    72  
    73  	// Macros specifies list of custom macros along with the values. This is used while forming
    74  	// the tracker URLs, where PBS will replace the Custom Macro with its value with url-encoding
    75  	Macros map[string]string `json:"macros,omitempty"`
    76  
    77  	// NoSale specifies bidders with whom the publisher has a legal relationship where the
    78  	// passing of personally identifiable information doesn't constitute a sale per CCPA law.
    79  	// The array may contain a single sstar ('*') entry to represent all bidders.
    80  	NoSale []string `json:"nosale,omitempty"`
    81  
    82  	// ReturnAllBidStatus if true populates bidresponse.ext.prebid.seatnonbid with all bids which was
    83  	// either rejected, nobid, input error
    84  	ReturnAllBidStatus bool `json:"returnallbidstatus,omitempty"`
    85  
    86  	// Trace controls the level of detail in the output information returned from executing hooks.
    87  	// There are two options:
    88  	// - verbose: sets maximum level of output information
    89  	// - basic: excludes debugmessages and analytic_tags from output
    90  	// any other value or an empty string disables trace output at all.
    91  	Trace string `json:"trace,omitempty"`
    92  }
    93  
    94  type AdServerTarget struct {
    95  	Key    string `json:"key,omitempty"`
    96  	Source string `json:"source,omitempty"`
    97  	Value  string `json:"value,omitempty"`
    98  }
    99  
   100  // Experiment defines if experimental features are available for the request
   101  type Experiment struct {
   102  	AdsCert *AdsCert `json:"adscert,omitempty"`
   103  }
   104  
   105  // AdsCert defines if Call Sign feature is enabled for request
   106  type AdsCert struct {
   107  	Enabled bool `json:"enabled,omitempty"`
   108  }
   109  
   110  type BidderConfig struct {
   111  	Bidders []string `json:"bidders,omitempty"`
   112  	Config  *Config  `json:"config,omitempty"`
   113  }
   114  
   115  type Config struct {
   116  	ORTB2 *ORTB2 `json:"ortb2,omitempty"`
   117  }
   118  
   119  type ORTB2 struct { //First party data
   120  	Site json.RawMessage `json:"site,omitempty"`
   121  	App  json.RawMessage `json:"app,omitempty"`
   122  	User json.RawMessage `json:"user,omitempty"`
   123  }
   124  
   125  type ExtRequestCurrency struct {
   126  	ConversionRates map[string]map[string]float64 `json:"rates"`
   127  	UsePBSRates     *bool                         `json:"usepbsrates"`
   128  }
   129  
   130  // ExtRequestPrebid defines the contract for bidrequest.ext.prebid.schains
   131  type ExtRequestPrebidSChain struct {
   132  	Bidders []string             `json:"bidders,omitempty"`
   133  	SChain  openrtb2.SupplyChain `json:"schain"`
   134  }
   135  
   136  // ExtRequestPrebidChannel defines the contract for bidrequest.ext.prebid.channel
   137  type ExtRequestPrebidChannel struct {
   138  	Name    string `json:"name"`
   139  	Version string `json:"version"`
   140  }
   141  
   142  // ExtRequestPrebidCache defines the contract for bidrequest.ext.prebid.cache
   143  type ExtRequestPrebidCache struct {
   144  	Bids    *ExtRequestPrebidCacheBids `json:"bids,omitempty"`
   145  	VastXML *ExtRequestPrebidCacheVAST `json:"vastxml,omitempty"`
   146  }
   147  
   148  type ExtRequestPrebidServer struct {
   149  	ExternalUrl string `json:"externalurl"`
   150  	GvlID       int    `json:"gvlid"`
   151  	DataCenter  string `json:"datacenter"`
   152  }
   153  
   154  // ExtRequestPrebidCacheBids defines the contract for bidrequest.ext.prebid.cache.bids
   155  type ExtRequestPrebidCacheBids struct {
   156  	ReturnCreative *bool `json:"returnCreative,omitempty"`
   157  }
   158  
   159  // ExtRequestPrebidCacheVAST defines the contract for bidrequest.ext.prebid.cache.vastxml
   160  type ExtRequestPrebidCacheVAST struct {
   161  	ReturnCreative *bool `json:"returnCreative,omitempty"`
   162  }
   163  
   164  // ExtRequestPrebidBidAdjustments defines the contract for bidrequest.ext.prebid.bidadjustments
   165  type ExtRequestPrebidBidAdjustments struct {
   166  	MediaType MediaType `json:"mediatype,omitempty"`
   167  }
   168  
   169  // AdjustmentsByDealID maps a dealID to a slice of bid adjustments
   170  type AdjustmentsByDealID map[string][]Adjustment
   171  
   172  // MediaType defines contract for bidrequest.ext.prebid.bidadjustments.mediatype
   173  // BidderName will map to a DealID that will map to a slice of bid adjustments
   174  type MediaType struct {
   175  	Banner         map[BidderName]AdjustmentsByDealID `json:"banner,omitempty"`
   176  	VideoInstream  map[BidderName]AdjustmentsByDealID `json:"video-instream,omitempty"`
   177  	VideoOutstream map[BidderName]AdjustmentsByDealID `json:"video-outstream,omitempty"`
   178  	Audio          map[BidderName]AdjustmentsByDealID `json:"audio,omitempty"`
   179  	Native         map[BidderName]AdjustmentsByDealID `json:"native,omitempty"`
   180  	WildCard       map[BidderName]AdjustmentsByDealID `json:"*,omitempty"`
   181  }
   182  
   183  // Adjustment defines the object that will be present in the slice of bid adjustments found from MediaType map
   184  type Adjustment struct {
   185  	Type     string  `json:"adjtype,omitempty"`
   186  	Value    float64 `json:"value,omitempty"`
   187  	Currency string  `json:"currency,omitempty"`
   188  }
   189  
   190  // ExtRequestTargeting defines the contract for bidrequest.ext.prebid.targeting
   191  type ExtRequestTargeting struct {
   192  	PriceGranularity          *PriceGranularity         `json:"pricegranularity,omitempty"`
   193  	MediaTypePriceGranularity MediaTypePriceGranularity `json:"mediatypepricegranularity,omitempty"`
   194  	IncludeWinners            *bool                     `json:"includewinners,omitempty"`
   195  	IncludeBidderKeys         *bool                     `json:"includebidderkeys,omitempty"`
   196  	IncludeBrandCategory      *ExtIncludeBrandCategory  `json:"includebrandcategory,omitempty"`
   197  	IncludeFormat             bool                      `json:"includeformat,omitempty"`
   198  	DurationRangeSec          []int                     `json:"durationrangesec,omitempty"`
   199  	PreferDeals               bool                      `json:"preferdeals,omitempty"`
   200  	AppendBidderNames         bool                      `json:"appendbiddernames,omitempty"`
   201  }
   202  
   203  type ExtIncludeBrandCategory struct {
   204  	PrimaryAdServer     int    `json:"primaryadserver"`
   205  	Publisher           string `json:"publisher"`
   206  	WithCategory        bool   `json:"withcategory"`
   207  	TranslateCategories *bool  `json:"translatecategories,omitempty"`
   208  }
   209  
   210  // MediaTypePriceGranularity specify price granularity configuration at the bid type level
   211  type MediaTypePriceGranularity struct {
   212  	Banner *PriceGranularity `json:"banner,omitempty"`
   213  	Video  *PriceGranularity `json:"video,omitempty"`
   214  	Native *PriceGranularity `json:"native,omitempty"`
   215  }
   216  
   217  // PriceGranularity defines the allowed values for bidrequest.ext.prebid.targeting.pricegranularity
   218  // or bidrequest.ext.prebid.targeting.mediatypepricegranularity.banner|video|native
   219  type PriceGranularity struct {
   220  	Precision *int               `json:"precision,omitempty"`
   221  	Ranges    []GranularityRange `json:"ranges,omitempty"`
   222  }
   223  
   224  type PriceGranularityRaw PriceGranularity
   225  
   226  // GranularityRange struct defines a range of prices used by PriceGranularity
   227  type GranularityRange struct {
   228  	Min       float64 `json:"min"`
   229  	Max       float64 `json:"max"`
   230  	Increment float64 `json:"increment"`
   231  }
   232  
   233  func (pg *PriceGranularity) UnmarshalJSON(b []byte) error {
   234  	// price granularity used to be a string referencing a predefined value, try to parse
   235  	// and map the legacy string before falling back to the modern custom model.
   236  	legacyID := ""
   237  	if err := json.Unmarshal(b, &legacyID); err == nil {
   238  		if legacyValue, ok := NewPriceGranularityFromLegacyID(legacyID); ok {
   239  			*pg = legacyValue
   240  			return nil
   241  		}
   242  	}
   243  
   244  	// use a type-alias to avoid calling back into this UnmarshalJSON implementation
   245  	modernValue := PriceGranularityRaw{}
   246  	err := json.Unmarshal(b, &modernValue)
   247  	if err == nil {
   248  		*pg = (PriceGranularity)(modernValue)
   249  	}
   250  	return err
   251  }
   252  
   253  func NewPriceGranularityDefault() PriceGranularity {
   254  	pg, _ := NewPriceGranularityFromLegacyID("medium")
   255  	return pg
   256  }
   257  
   258  // NewPriceGranularityFromLegacyID converts a legacy string into the new PriceGranularity structure.
   259  func NewPriceGranularityFromLegacyID(v string) (PriceGranularity, bool) {
   260  	precision2 := 2
   261  
   262  	switch v {
   263  	case "low":
   264  		return PriceGranularity{
   265  			Precision: &precision2,
   266  			Ranges: []GranularityRange{{
   267  				Min:       0,
   268  				Max:       5,
   269  				Increment: 0.5}},
   270  		}, true
   271  
   272  	case "med", "medium":
   273  		return PriceGranularity{
   274  			Precision: &precision2,
   275  			Ranges: []GranularityRange{{
   276  				Min:       0,
   277  				Max:       20,
   278  				Increment: 0.1}},
   279  		}, true
   280  
   281  	case "high":
   282  		return PriceGranularity{
   283  			Precision: &precision2,
   284  			Ranges: []GranularityRange{{
   285  				Min:       0,
   286  				Max:       20,
   287  				Increment: 0.01}},
   288  		}, true
   289  
   290  	case "auto":
   291  		return PriceGranularity{
   292  			Precision: &precision2,
   293  			Ranges: []GranularityRange{
   294  				{
   295  					Min:       0,
   296  					Max:       5,
   297  					Increment: 0.05,
   298  				},
   299  				{
   300  					Min:       5,
   301  					Max:       10,
   302  					Increment: 0.1,
   303  				},
   304  				{
   305  					Min:       10,
   306  					Max:       20,
   307  					Increment: 0.5,
   308  				},
   309  			},
   310  		}, true
   311  
   312  	case "dense":
   313  		return PriceGranularity{
   314  			Precision: &precision2,
   315  			Ranges: []GranularityRange{
   316  				{
   317  					Min:       0,
   318  					Max:       3,
   319  					Increment: 0.01,
   320  				},
   321  				{
   322  					Min:       3,
   323  					Max:       8,
   324  					Increment: 0.05,
   325  				},
   326  				{
   327  					Min:       8,
   328  					Max:       20,
   329  					Increment: 0.5,
   330  				},
   331  			},
   332  		}, true
   333  	}
   334  
   335  	return PriceGranularity{}, false
   336  }
   337  
   338  // ExtRequestPrebidData defines Prebid's First Party Data (FPD) and related bid request options.
   339  type ExtRequestPrebidData struct {
   340  	EidPermissions []ExtRequestPrebidDataEidPermission `json:"eidpermissions"`
   341  	Bidders        []string                            `json:"bidders,omitempty"`
   342  }
   343  
   344  // ExtRequestPrebidDataEidPermission defines a filter rule for filter user.ext.eids
   345  type ExtRequestPrebidDataEidPermission struct {
   346  	Source  string   `json:"source"`
   347  	Bidders []string `json:"bidders"`
   348  }
   349  
   350  type ExtRequestSdk struct {
   351  	Renderers []ExtRequestSdkRenderer `json:"renderers,omitempty"`
   352  }
   353  
   354  type ExtRequestSdkRenderer struct {
   355  	Name    string          `json:"name,omitempty"`
   356  	Version string          `json:"version,omitempty"`
   357  	Data    json.RawMessage `json:"data,omitempty"`
   358  }
   359  
   360  type ExtMultiBid struct {
   361  	Bidder                 string   `json:"bidder,omitempty"`
   362  	Bidders                []string `json:"bidders,omitempty"`
   363  	MaxBids                *int     `json:"maxbids,omitempty"`
   364  	TargetBidderCodePrefix string   `json:"targetbiddercodeprefix,omitempty"`
   365  }
   366  
   367  func (m ExtMultiBid) String() string {
   368  	maxBid := "<nil>"
   369  	if m.MaxBids != nil {
   370  		maxBid = fmt.Sprintf("%d", *m.MaxBids)
   371  	}
   372  	return fmt.Sprintf("{Bidder:%s, Bidders:%v, MaxBids:%s, TargetBidderCodePrefix:%s}", m.Bidder, m.Bidders, maxBid, m.TargetBidderCodePrefix)
   373  }
   374  
   375  func (erp *ExtRequestPrebid) Clone() *ExtRequestPrebid {
   376  	if erp == nil {
   377  		return nil
   378  	}
   379  
   380  	clone := *erp
   381  	clone.Aliases = maputil.Clone(erp.Aliases)
   382  	clone.AliasGVLIDs = maputil.Clone(erp.AliasGVLIDs)
   383  	clone.BidAdjustmentFactors = maputil.Clone(erp.BidAdjustmentFactors)
   384  
   385  	if erp.BidderConfigs != nil {
   386  		clone.BidderConfigs = make([]BidderConfig, len(erp.BidderConfigs))
   387  		for i, bc := range erp.BidderConfigs {
   388  			clonedBidderConfig := BidderConfig{Bidders: sliceutil.Clone(bc.Bidders)}
   389  			if bc.Config != nil {
   390  				config := &Config{ORTB2: ptrutil.Clone(bc.Config.ORTB2)}
   391  				clonedBidderConfig.Config = config
   392  			}
   393  			clone.BidderConfigs[i] = clonedBidderConfig
   394  		}
   395  	}
   396  
   397  	if erp.Cache != nil {
   398  		clone.Cache = &ExtRequestPrebidCache{}
   399  		if erp.Cache.Bids != nil {
   400  			clone.Cache.Bids = &ExtRequestPrebidCacheBids{}
   401  			clone.Cache.Bids.ReturnCreative = ptrutil.Clone(erp.Cache.Bids.ReturnCreative)
   402  		}
   403  		if erp.Cache.VastXML != nil {
   404  			clone.Cache.VastXML = &ExtRequestPrebidCacheVAST{}
   405  			clone.Cache.VastXML.ReturnCreative = ptrutil.Clone(erp.Cache.VastXML.ReturnCreative)
   406  		}
   407  	}
   408  
   409  	if erp.Channel != nil {
   410  		channel := *erp.Channel
   411  		clone.Channel = &channel
   412  	}
   413  
   414  	if erp.CurrencyConversions != nil {
   415  		newConvRates := make(map[string]map[string]float64, len(erp.CurrencyConversions.ConversionRates))
   416  		for key, val := range erp.CurrencyConversions.ConversionRates {
   417  			newConvRates[key] = maputil.Clone(val)
   418  		}
   419  		clone.CurrencyConversions = &ExtRequestCurrency{ConversionRates: newConvRates}
   420  		if erp.CurrencyConversions.UsePBSRates != nil {
   421  			clone.CurrencyConversions.UsePBSRates = ptrutil.ToPtr(*erp.CurrencyConversions.UsePBSRates)
   422  		}
   423  	}
   424  
   425  	if erp.Data != nil {
   426  		clone.Data = &ExtRequestPrebidData{Bidders: sliceutil.Clone(erp.Data.Bidders)}
   427  		if erp.Data.EidPermissions != nil {
   428  			newEidPermissions := make([]ExtRequestPrebidDataEidPermission, len(erp.Data.EidPermissions))
   429  			for i, eidp := range erp.Data.EidPermissions {
   430  				newEidPermissions[i] = ExtRequestPrebidDataEidPermission{
   431  					Source:  eidp.Source,
   432  					Bidders: sliceutil.Clone(eidp.Bidders),
   433  				}
   434  			}
   435  			clone.Data.EidPermissions = newEidPermissions
   436  		}
   437  	}
   438  
   439  	if erp.Experiment != nil {
   440  		clone.Experiment = &Experiment{}
   441  		if erp.Experiment.AdsCert != nil {
   442  			clone.Experiment.AdsCert = ptrutil.ToPtr(*erp.Experiment.AdsCert)
   443  		}
   444  	}
   445  
   446  	if erp.MultiBid != nil {
   447  		clone.MultiBid = make([]*ExtMultiBid, len(erp.MultiBid))
   448  		for i, mulBid := range erp.MultiBid {
   449  			newMulBid := &ExtMultiBid{
   450  				Bidder:                 mulBid.Bidder,
   451  				Bidders:                sliceutil.Clone(mulBid.Bidders),
   452  				TargetBidderCodePrefix: mulBid.TargetBidderCodePrefix,
   453  			}
   454  			if mulBid.MaxBids != nil {
   455  				newMulBid.MaxBids = ptrutil.ToPtr(*mulBid.MaxBids)
   456  			}
   457  			clone.MultiBid[i] = newMulBid
   458  		}
   459  	}
   460  
   461  	if erp.SChains != nil {
   462  		clone.SChains = make([]*ExtRequestPrebidSChain, len(erp.SChains))
   463  		for i, schain := range erp.SChains {
   464  			newChain := *schain
   465  			newNodes := sliceutil.Clone(schain.SChain.Nodes)
   466  			for j, node := range newNodes {
   467  				if node.HP != nil {
   468  					newNodes[j].HP = ptrutil.ToPtr(*newNodes[j].HP)
   469  				}
   470  			}
   471  			newChain.SChain.Nodes = newNodes
   472  			clone.SChains[i] = &newChain
   473  		}
   474  	}
   475  
   476  	clone.Server = ptrutil.Clone(erp.Server)
   477  
   478  	clone.StoredRequest = ptrutil.Clone(erp.StoredRequest)
   479  
   480  	if erp.Targeting != nil {
   481  		newTargeting := &ExtRequestTargeting{
   482  			IncludeFormat:     erp.Targeting.IncludeFormat,
   483  			DurationRangeSec:  sliceutil.Clone(erp.Targeting.DurationRangeSec),
   484  			PreferDeals:       erp.Targeting.PreferDeals,
   485  			AppendBidderNames: erp.Targeting.AppendBidderNames,
   486  		}
   487  		if erp.Targeting.PriceGranularity != nil {
   488  			newPriceGranularity := &PriceGranularity{
   489  				Ranges: sliceutil.Clone(erp.Targeting.PriceGranularity.Ranges),
   490  			}
   491  			newPriceGranularity.Precision = ptrutil.Clone(erp.Targeting.PriceGranularity.Precision)
   492  			newTargeting.PriceGranularity = newPriceGranularity
   493  		}
   494  		newTargeting.IncludeWinners = ptrutil.Clone(erp.Targeting.IncludeWinners)
   495  		newTargeting.IncludeBidderKeys = ptrutil.Clone(erp.Targeting.IncludeBidderKeys)
   496  		if erp.Targeting.IncludeBrandCategory != nil {
   497  			newIncludeBrandCategory := *erp.Targeting.IncludeBrandCategory
   498  			newIncludeBrandCategory.TranslateCategories = ptrutil.Clone(erp.Targeting.IncludeBrandCategory.TranslateCategories)
   499  			newTargeting.IncludeBrandCategory = &newIncludeBrandCategory
   500  		}
   501  		clone.Targeting = newTargeting
   502  	}
   503  
   504  	clone.NoSale = sliceutil.Clone(erp.NoSale)
   505  
   506  	if erp.AlternateBidderCodes != nil {
   507  		newAlternateBidderCodes := ExtAlternateBidderCodes{Enabled: erp.AlternateBidderCodes.Enabled}
   508  		if erp.AlternateBidderCodes.Bidders != nil {
   509  			newBidders := make(map[string]ExtAdapterAlternateBidderCodes, len(erp.AlternateBidderCodes.Bidders))
   510  			for key, val := range erp.AlternateBidderCodes.Bidders {
   511  				newBidders[key] = ExtAdapterAlternateBidderCodes{
   512  					Enabled:            val.Enabled,
   513  					AllowedBidderCodes: sliceutil.Clone(val.AllowedBidderCodes),
   514  				}
   515  			}
   516  			newAlternateBidderCodes.Bidders = newBidders
   517  		}
   518  		clone.AlternateBidderCodes = &newAlternateBidderCodes
   519  	}
   520  
   521  	if erp.Floors != nil {
   522  		clonedFloors := *erp.Floors
   523  		clonedFloors.Location = ptrutil.Clone(erp.Floors.Location)
   524  		if erp.Floors.Data != nil {
   525  			clonedData := *erp.Floors.Data
   526  			if erp.Floors.Data.ModelGroups != nil {
   527  				clonedData.ModelGroups = make([]PriceFloorModelGroup, len(erp.Floors.Data.ModelGroups))
   528  				for i, pfmg := range erp.Floors.Data.ModelGroups {
   529  					clonedData.ModelGroups[i] = pfmg
   530  					clonedData.ModelGroups[i].ModelWeight = ptrutil.Clone(pfmg.ModelWeight)
   531  					clonedData.ModelGroups[i].Schema.Fields = sliceutil.Clone(pfmg.Schema.Fields)
   532  					clonedData.ModelGroups[i].Values = maputil.Clone(pfmg.Values)
   533  				}
   534  			}
   535  			clonedFloors.Data = &clonedData
   536  		}
   537  		if erp.Floors.Enforcement != nil {
   538  			clonedFloors.Enforcement = &PriceFloorEnforcement{
   539  				EnforceJS:     ptrutil.Clone(erp.Floors.Enforcement.EnforceJS),
   540  				EnforcePBS:    ptrutil.Clone(erp.Floors.Enforcement.EnforcePBS),
   541  				FloorDeals:    ptrutil.Clone(erp.Floors.Enforcement.FloorDeals),
   542  				BidAdjustment: ptrutil.Clone(erp.Floors.Enforcement.BidAdjustment),
   543  				EnforceRate:   erp.Floors.Enforcement.EnforceRate,
   544  			}
   545  		}
   546  		clonedFloors.Enabled = ptrutil.Clone(erp.Floors.Enabled)
   547  		clonedFloors.Skipped = ptrutil.Clone(erp.Floors.Skipped)
   548  		clone.Floors = &clonedFloors
   549  	}
   550  	if erp.MultiBidMap != nil {
   551  		clone.MultiBidMap = make(map[string]ExtMultiBid, len(erp.MultiBidMap))
   552  		for k, v := range erp.MultiBidMap {
   553  			// Make v a deep copy of the ExtMultiBid struct
   554  			v.Bidders = sliceutil.Clone(v.Bidders)
   555  			v.MaxBids = ptrutil.Clone(v.MaxBids)
   556  			clone.MultiBidMap[k] = v
   557  		}
   558  	}
   559  	clone.AdServerTargeting = sliceutil.Clone(erp.AdServerTargeting)
   560  
   561  	return &clone
   562  }