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 }