-
Notifications
You must be signed in to change notification settings - Fork 731
/
request.go
288 lines (257 loc) · 8.67 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
package openrtb_ext
import (
"encoding/json"
"errors"
)
// FirstPartyDataContextExtKey defines the field name within bidrequest.ext reserved
// for first party data support.
const FirstPartyDataContextExtKey string = "context"
// 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"`
SChains []*ExtRequestPrebidSChain `json:"schains,omitempty"`
StoredRequest *ExtStoredRequest `json:"storedrequest,omitempty"`
Targeting *ExtRequestTargeting `json:"targeting,omitempty"`
SupportDeals bool `json:"supportdeals,omitempty"`
Debug bool `json:"debug,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"`
}
// 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"`
}
// 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"`
}
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 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
if pgraw.Ranges[i].Min < prevMax {
return errors.New("Price granularity error: overlapping granularity ranges")
}
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,
},
},
}