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 }