github.com/prebid/prebid-server/v2@v2.18.0/openrtb_ext/request.go (about)

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