forked from prebid/prebid-server
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrequest.go
345 lines (302 loc) · 11 KB
/
request.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
package openrtb_ext
import (
"encoding/json"
"errors"
)
// FirstPartyDataExtKey defines a field name within request.ext and request.imp.ext reserved for first party data.
const FirstPartyDataExtKey = "data"
// FirstPartyDataContextExtKey defines a field name within request.ext and request.imp.ext reserved for first party data.
const FirstPartyDataContextExtKey = "context"
// SKAdNExtKey defines the field name within request.ext reserved for Apple's SKAdNetwork.
const SKAdNExtKey = "skadn"
// NativeExchangeSpecificLowerBound defines the lower threshold of exchange specific types for native ads. There is no upper bound.
const NativeExchangeSpecificLowerBound = 500
const MaxDecimalFigures int = 15
// ExtRequest defines the contract for bidrequest.ext
type ExtRequest struct {
Prebid ExtRequestPrebid `json:"prebid"`
}
// ExtRequestPrebid defines the contract for bidrequest.ext.prebid
type ExtRequestPrebid struct {
Aliases map[string]string `json:"aliases,omitempty"`
BidAdjustmentFactors map[string]float64 `json:"bidadjustmentfactors,omitempty"`
Cache *ExtRequestPrebidCache `json:"cache,omitempty"`
Channel *ExtRequestPrebidChannel `json:"channel,omitempty"`
Data *ExtRequestPrebidData `json:"data,omitempty"`
Debug bool `json:"debug,omitempty"`
Events json.RawMessage `json:"events,omitempty"`
SChains []*ExtRequestPrebidSChain `json:"schains,omitempty"`
StoredRequest *ExtStoredRequest `json:"storedrequest,omitempty"`
SupportDeals bool `json:"supportdeals,omitempty"`
Targeting *ExtRequestTargeting `json:"targeting,omitempty"`
BidderParams json.RawMessage `json:"bidderparams,omitempty"`
// NoSale specifies bidders with whom the publisher has a legal relationship where the
// passing of personally identifiable information doesn't constitute a sale per CCPA law.
// The array may contain a single sstar ('*') entry to represent all bidders.
NoSale []string `json:"nosale,omitempty"`
CurrencyConversions *ExtRequestCurrency `json:"currency,omitempty"`
BidderConfigs []BidderConfig `json:"bidderconfig,omitempty"`
}
type BidderConfig struct {
Bidders []string `json:"bidders,omitempty"`
Config *Config `json:"config,omitempty"`
}
type Config struct {
ORTB2 *ORTB2 `json:"ortb2,omitempty"`
}
type ORTB2 struct { //First party data
Site map[string]json.RawMessage `json:"site,omitempty"`
App map[string]json.RawMessage `json:"app,omitempty"`
User map[string]json.RawMessage `json:"user,omitempty"`
}
type ExtRequestCurrency struct {
ConversionRates map[string]map[string]float64 `json:"rates"`
UsePBSRates *bool `json:"usepbsrates"`
}
// ExtRequestPrebid defines the contract for bidrequest.ext.prebid.schains
type ExtRequestPrebidSChain struct {
Bidders []string `json:"bidders,omitempty"`
SChain ExtRequestPrebidSChainSChain `json:"schain"`
}
// ExtRequestPrebidSChainSChain defines the contract for bidrequest.ext.prebid.schains[i].schain
type ExtRequestPrebidSChainSChain struct {
Complete int `json:"complete"`
Nodes []*ExtRequestPrebidSChainSChainNode `json:"nodes"`
Ver string `json:"ver"`
Ext json.RawMessage `json:"ext,omitempty"`
}
// ExtRequestPrebidSChainSChainNode defines the contract for bidrequest.ext.prebid.schains[i].schain[i].nodes
type ExtRequestPrebidSChainSChainNode struct {
ASI string `json:"asi"`
SID string `json:"sid"`
RID string `json:"rid,omitempty"`
Name string `json:"name,omitempty"`
Domain string `json:"domain,omitempty"`
HP int `json:"hp"`
Ext json.RawMessage `json:"ext,omitempty"`
}
// SourceExt defines the contract for bidrequest.source.ext
type SourceExt struct {
SChain ExtRequestPrebidSChainSChain `json:"schain"`
}
// ExtRequestPrebidChannel defines the contract for bidrequest.ext.prebid.channel
type ExtRequestPrebidChannel struct {
Name string `json:"name"`
Version string `json:"version"`
}
// ExtRequestPrebidCache defines the contract for bidrequest.ext.prebid.cache
type ExtRequestPrebidCache struct {
Bids *ExtRequestPrebidCacheBids `json:"bids"`
VastXML *ExtRequestPrebidCacheVAST `json:"vastxml"`
}
// UnmarshalJSON prevents nil bids arguments.
func (ert *ExtRequestPrebidCache) UnmarshalJSON(b []byte) error {
type typesAlias ExtRequestPrebidCache // Prevents infinite UnmarshalJSON loops
var proxy typesAlias
if err := json.Unmarshal(b, &proxy); err != nil {
return err
}
if proxy.Bids == nil && proxy.VastXML == nil {
return errors.New(`request.ext.prebid.cache requires one of the "bids" or "vastxml" properties`)
}
*ert = ExtRequestPrebidCache(proxy)
return nil
}
// ExtRequestPrebidCacheBids defines the contract for bidrequest.ext.prebid.cache.bids
type ExtRequestPrebidCacheBids struct {
ReturnCreative *bool `json:"returnCreative"`
}
// ExtRequestPrebidCacheVAST defines the contract for bidrequest.ext.prebid.cache.vastxml
type ExtRequestPrebidCacheVAST struct {
ReturnCreative *bool `json:"returnCreative"`
}
// ExtRequestTargeting defines the contract for bidrequest.ext.prebid.targeting
type ExtRequestTargeting struct {
PriceGranularity PriceGranularity `json:"pricegranularity"`
IncludeWinners bool `json:"includewinners"`
IncludeBidderKeys bool `json:"includebidderkeys"`
IncludeBrandCategory *ExtIncludeBrandCategory `json:"includebrandcategory"`
IncludeFormat bool `json:"includeformat"`
DurationRangeSec []int `json:"durationrangesec"`
PreferDeals bool `json:"preferdeals"`
AppendBidderNames bool `json:"appendbiddernames,omitempty"`
}
type ExtIncludeBrandCategory struct {
PrimaryAdServer int `json:"primaryadserver"`
Publisher string `json:"publisher"`
WithCategory bool `json:"withcategory"`
TranslateCategories *bool `json:"translatecategories,omitempty"`
}
// Make an unmarshaller that will set a default PriceGranularity
func (ert *ExtRequestTargeting) UnmarshalJSON(b []byte) error {
if string(b) == "null" {
return nil
}
// define separate type to prevent infinite recursive calls to UnmarshalJSON
type extRequestTargetingDefaults ExtRequestTargeting
defaults := &extRequestTargetingDefaults{
PriceGranularity: priceGranularityMed,
IncludeWinners: true,
IncludeBidderKeys: true,
}
err := json.Unmarshal(b, defaults)
if err == nil {
if !defaults.IncludeWinners && !defaults.IncludeBidderKeys {
return errors.New("ext.prebid.targeting: At least one of includewinners or includebidderkeys must be enabled to enable targeting support")
}
*ert = ExtRequestTargeting(*defaults)
}
return err
}
// PriceGranularity defines the allowed values for bidrequest.ext.prebid.targeting.pricegranularity
type PriceGranularity struct {
Precision int `json:"precision,omitempty"`
Ranges []GranularityRange `json:"ranges,omitempty"`
}
type PriceGranularityRaw PriceGranularity
// GranularityRange struct defines a range of prices used by PriceGranularity
type GranularityRange struct {
Min float64 `json:"min"`
Max float64 `json:"max"`
Increment float64 `json:"increment"`
}
// UnmarshalJSON : custom unmarshaller to handle legacy string granularites.
func (pg *PriceGranularity) UnmarshalJSON(b []byte) error {
// We default to medium
if len(b) == 0 {
*pg = priceGranularityMed
return nil
}
// First check for legacy strings
var pgString string
err := json.Unmarshal(b, &pgString)
if err == nil {
*pg = PriceGranularityFromString(pgString)
if len(pg.Ranges) > 0 {
// Only exit if we matched something, else we try processing as custom granularity
// This way we error as expecting the new custom granularity standard.
return nil
}
}
// Not legacy, so we do a normal Unmarshal
pgraw := PriceGranularityRaw{}
pgraw.Precision = 2
err = json.Unmarshal(b, &pgraw)
if err != nil {
return err
}
if pgraw.Precision < 0 {
return errors.New("Price granularity error: precision must be non-negative")
}
if pgraw.Precision > MaxDecimalFigures {
return errors.New("Price granularity error: precision of more than 15 significant figures is not supported")
}
if len(pgraw.Ranges) > 0 {
var prevMax float64 = 0
for i, gr := range pgraw.Ranges {
if gr.Max <= prevMax {
return errors.New("Price granularity error: range list must be ordered with increasing \"max\"")
}
if gr.Increment <= 0.0 {
return errors.New("Price granularity error: increment must be a nonzero positive number")
}
// Enforce that we don't read "min" from the request
pgraw.Ranges[i].Min = prevMax
prevMax = gr.Max
}
*pg = PriceGranularity(pgraw)
return nil
}
// Default to medium if no ranges are specified
*pg = priceGranularityMed
return nil
}
// PriceGranularityFromString converts a legacy string into the new PriceGranularity
func PriceGranularityFromString(gran string) PriceGranularity {
switch gran {
case "low":
return priceGranularityLow
case "med", "medium":
// Seems that PBS was written with medium = "med", so hacking that in
return priceGranularityMed
case "high":
return priceGranularityHigh
case "auto":
return priceGranularityAuto
case "dense":
return priceGranularityDense
}
// Return empty if not matched
return PriceGranularity{}
}
var priceGranularityLow = PriceGranularity{
Precision: 2,
Ranges: []GranularityRange{{
Min: 0,
Max: 5,
Increment: 0.5}},
}
var priceGranularityMed = PriceGranularity{
Precision: 2,
Ranges: []GranularityRange{{
Min: 0,
Max: 20,
Increment: 0.1}},
}
var priceGranularityHigh = PriceGranularity{
Precision: 2,
Ranges: []GranularityRange{{
Min: 0,
Max: 20,
Increment: 0.01}},
}
var priceGranularityDense = PriceGranularity{
Precision: 2,
Ranges: []GranularityRange{
{
Min: 0,
Max: 3,
Increment: 0.01,
},
{
Min: 3,
Max: 8,
Increment: 0.05,
},
{
Min: 8,
Max: 20,
Increment: 0.5,
},
},
}
var priceGranularityAuto = PriceGranularity{
Precision: 2,
Ranges: []GranularityRange{
{
Min: 0,
Max: 5,
Increment: 0.05,
},
{
Min: 5,
Max: 10,
Increment: 0.1,
},
{
Min: 10,
Max: 20,
Increment: 0.5,
},
},
}
// ExtRequestPrebidData defines Prebid's First Party Data (FPD) and related bid request options.
type ExtRequestPrebidData struct {
EidPermissions []ExtRequestPrebidDataEidPermission `json:"eidpermissions"`
Bidders []string `json:"bidders,omitempty"`
}
// ExtRequestPrebidDataEidPermission defines a filter rule for filter user.ext.eids
type ExtRequestPrebidDataEidPermission struct {
Source string `json:"source"`
Bidders []string `json:"bidders"`
}