diff --git a/impression.go b/impression.go index eeaee85..589daf6 100644 --- a/impression.go +++ b/impression.go @@ -32,8 +32,9 @@ type Impression struct { BidFloor float64 `json:"bidfloor,omitempty"` // Bid floor for this impression in CPM BidFloorCurrency string `json:"bidfloorcur,omitempty"` // Currency of bid floor Secure NumberOrString `json:"secure,omitempty"` // Flag to indicate whether the impression requires secure HTTPS URL creative assets and markup. - Exp int `json:"exp,omitempty"` // Advisory as to the number of seconds that may elapse between the auction and the actual impression. - IFrameBusters []string `json:"iframebuster,omitempty"` // Array of names for supportediframe busters. + Quantity *Quantity `json:"qty,omitempty"` // Includes the impression multiplier, and describes its source. + Exp int `json:"exp,omitempty"` // Advisory as to the number of seconds that may elapse between the auction and the actual impression. + IFrameBusters []string `json:"iframebuster,omitempty"` // Array of names for supportediframe busters. Ext json.RawMessage `json:"ext,omitempty"` } @@ -66,6 +67,11 @@ func (imp *Impression) Validate() error { return err } } + if imp.Quantity != nil { + if err := imp.Quantity.Validate(); err != nil { + return err + } + } return nil } diff --git a/impression_test.go b/impression_test.go index 958816d..95046a3 100644 --- a/impression_test.go +++ b/impression_test.go @@ -31,6 +31,9 @@ func TestImpression(t *testing.T) { }, }, }, + Quantity: &Quantity{ + Multiplier: 1.0, + }, } if got := subject; !reflect.DeepEqual(exp, got) { t.Errorf("expected %+v, got %+v", exp, got) diff --git a/quantity.go b/quantity.go new file mode 100644 index 0000000..9ec3fee --- /dev/null +++ b/quantity.go @@ -0,0 +1,37 @@ +package openrtb + +import ( + "encoding/json" + "errors" +) + +var ( + ErrMissingMultiplier = errors.New("openrtb: qty.multiplier is required") + ErrMissingMeasurementVendor = errors.New("openrtb: qty.vendor is required when qty.sourcetype is 1 (Measurement Vendor)") +) + +type MeasurementSourceType int + +const ( + MeasurementSourceTypeUnknown MeasurementSourceType = 0 + MeasurementSourceTypeMeasurementVendor MeasurementSourceType = 1 + MeasurementSourceTypePublisher MeasurementSourceType = 2 + MeasurementSourceTypeExchange MeasurementSourceType = 3 +) + +type Quantity struct { + Multiplier float64 `json:"multiplier"` + SourceType MeasurementSourceType `json:"sourcetype,omitempty"` + Vendor string `json:"vendor,omitempty"` + Ext *json.RawMessage `json:"ext,omitempty"` +} + +func (qty *Quantity) Validate() error { + if qty.Multiplier == 0 { + return ErrMissingMultiplier + } + if qty.SourceType == MeasurementSourceTypeMeasurementVendor && qty.Vendor == "" { + return ErrMissingMeasurementVendor + } + return nil +} diff --git a/quantity_test.go b/quantity_test.go new file mode 100644 index 0000000..2206437 --- /dev/null +++ b/quantity_test.go @@ -0,0 +1,55 @@ +package openrtb_test + +import ( + "reflect" + "testing" + + . "github.com/bsm/openrtb/v3" +) + +func TestQuantity(t *testing.T) { + var subject *Quantity + if err := fixture("quantity", &subject); err != nil { + t.Fatalf("expected no error, got %+v", err) + } + + exp := &Quantity{ + Multiplier: 3.14, + SourceType: MeasurementSourceTypePublisher, + } + + if !reflect.DeepEqual(exp, subject) { + t.Fatalf("expected %+v, got %+v", exp, subject) + } +} + +func TestQuantity_Validate(t *testing.T) { + subject := Quantity{} + if got := subject.Validate(); got != ErrMissingMultiplier { + t.Fatalf("expected %+v, got %+v", ErrMissingMultiplier, got) + } + + // If MeasurementSourceType is MeasurementSourceTypeMeasurementVendor, the + // Vendor field should not be empty. + subject = Quantity{ + Multiplier: 1.0, + SourceType: MeasurementSourceTypeMeasurementVendor, + } + if got := subject.Validate(); got != ErrMissingMeasurementVendor { + t.Fatalf("expected %+v, got %+v", ErrMissingMeasurementVendor, got) + } + + subject.Vendor = "TestVendor" // Should fix the invalid Quantity + if got := subject.Validate(); got != nil { + t.Fatalf("expected no error, got %+v", got) + } + + // All other value from MeasurementSourceType can be used without Vendor + subject = Quantity{ + Multiplier: 2.0, + SourceType: MeasurementSourceTypeUnknown, + } + if got := subject.Validate(); got != nil { + t.Fatalf("expected nil, got %+v", got) + } +} diff --git a/testdata/impression.json b/testdata/impression.json index a66434d..3d1bd45 100644 --- a/testdata/impression.json +++ b/testdata/impression.json @@ -15,5 +15,8 @@ "at": 2 } ] + }, + "qty": { + "multiplier": 1.0 } } diff --git a/testdata/quantity.json b/testdata/quantity.json new file mode 100644 index 0000000..1d03a4e --- /dev/null +++ b/testdata/quantity.json @@ -0,0 +1,4 @@ +{ + "multiplier": 3.14, + "sourcetype": 2 +}