diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..8a64d74 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,32 @@ +name: Test +on: + push: + branches: + - master + pull_request: + branches: + - master +jobs: + go: + runs-on: ubuntu-latest + strategy: + matrix: + go-version: [1.18.x, 1.19.x] + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.go-version }} + cache: true + - run: make test + golangci: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: 1.x + cache: true + - uses: golangci/golangci-lint-action@v3 + with: + version: latest diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dc6f263 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.makefile + +.idea +.vscode diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 62da91f..0000000 --- a/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: go -sudo: false -go: - - 1.6.4 - - 1.7.4 -install: - - go get -u -t ./... diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f731b7e --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2015 Black Square Media Ltd. All rights reserved. +(The MIT License) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Some test examples were taken from: +https://code.google.com/p/openrtb/wiki/OpenRTB_Examples diff --git a/README.md b/README.md index 92d134e..12f487b 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,12 @@ -# Go OpenRTB v2.x +# OpenRTB [![Build Status](https://travis-ci.org/UnityTech/openrtb.svg?branch=master)](https://travis-ci.org/UnityTech/openrtb) -OpenRTB implementation for Go +OpenRTB structs and validations for Go. + +## Requirements + +Requires Go 1.8+ for proper `json.RawMessage` marshaling. ## Installation @@ -14,13 +18,12 @@ go get github.com/UnityTech/openrtb ## Usage -Import the package: - ```go package main import ( "log" + "github.com/UnityTech/openrtb" ) @@ -32,38 +35,10 @@ func main() { defer file.Close() var req *openrtb.BidRequest - err = json.NewDecoder(file).Decode(&req) - if err != nil { + if err := json.NewDecoder(file).Decode(&req); err != nil { log.Fatal(err) } log.Printf("%+v\n", req) } ``` - -## Licence - - Copyright (c) 2015 Black Square Media Ltd. All rights reserved. - (The MIT License) - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - 'Software'), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - Some test examples were taken from: - https://code.google.com/p/openrtb/wiki/OpenRTB_Examples diff --git a/audio.go b/audio.go index 5dd8a03..2ef6b8f 100644 --- a/audio.go +++ b/audio.go @@ -10,7 +10,7 @@ var ( ErrInvalidAudioNoMimes = errors.New("openrtb: audio has no mimes") ) -// The "audio" object must be included directly in the impression object +// Audio object must be included directly in the impression object type Audio struct { Mimes []string `json:"mimes"` // Content MIME types supported. MinDuration int `json:"minduration,omitempty"` // Minimum video ad duration in seconds @@ -35,7 +35,7 @@ type Audio struct { type jsonAudio Audio -// Validates the object +// Validate the object func (a *Audio) Validate() error { if len(a.Mimes) == 0 { return ErrInvalidAudioNoMimes diff --git a/audio_test.go b/audio_test.go index 4a4937e..6cd95bd 100644 --- a/audio_test.go +++ b/audio_test.go @@ -1,58 +1,62 @@ -package openrtb +package openrtb_test import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" + "errors" + "reflect" + "testing" + + . "github.com/UnityTech/openrtb" ) -var _ = Describe("Audio", func() { +func TestAudio(t *testing.T) { var subject *Audio + if err := fixture("audio", &subject); err != nil { + t.Fatalf("expected no error, got %v", err) + } - BeforeEach(func() { - err := fixture("audio", &subject) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should parse correctly", func() { - Expect(subject).To(Equal(&Audio{ - Mimes: []string{ - "audio/mp4", - }, - MinDuration: 5, - MaxDuration: 30, - Protocols: []int{AudioProtocolDAAST1, AudioProtocolDAAST1Wrapper}, - Sequence: 1, - BAttr: []int{CreativeAttributeUserInitiated, CreativeAttributeWindowsDialogOrAlert}, - MaxExtended: 30, - MinBitrate: 300, - MaxBitrate: 1500, - Delivery: []int{ContentDeliveryProgressive}, - CompanionAd: []Banner{ - {W: 300, H: 250, ID: "1234567893-1", Pos: AdPosAboveFold, BAttr: []int{CreativeAttributeUserInitiated, CreativeAttributeWindowsDialogOrAlert}, ExpDir: []int{ExpDirRight, ExpDirDown}}, - {W: 728, H: 90, ID: "1234567893-2", Pos: AdPosAboveFold, BAttr: []int{CreativeAttributeUserInitiated, CreativeAttributeWindowsDialogOrAlert}}, - }, - API: []int{APIFrameworkVPAID1, APIFrameworkVPAID2}, - CompanionType: []int{VASTCompanionStatic, VASTCompanionHTML}, - })) - }) - - It("should validate", func() { - Expect((&Audio{ - MinDuration: 5, - MaxDuration: 30, - Protocols: []int{AudioProtocolDAAST1, AudioProtocolDAAST1Wrapper}, - Sequence: 1, - BAttr: []int{CreativeAttributeUserInitiated, CreativeAttributeWindowsDialogOrAlert}, - MaxExtended: 30, - MinBitrate: 300, - MaxBitrate: 1500, - Delivery: []int{ContentDeliveryProgressive}, - CompanionAd: []Banner{ - {W: 300, H: 250, ID: "1234567893-1", Pos: AdPosAboveFold, BAttr: []int{CreativeAttributeUserInitiated, CreativeAttributeWindowsDialogOrAlert}, ExpDir: []int{ExpDirRight, ExpDirDown}}, - {W: 728, H: 90, ID: "1234567893-2", Pos: AdPosAboveFold, BAttr: []int{CreativeAttributeUserInitiated, CreativeAttributeWindowsDialogOrAlert}}, - }, - CompanionType: []int{VASTCompanionStatic, VASTCompanionHTML}, - }).Validate()).To(Equal(ErrInvalidAudioNoMimes)) - }) + exp := &Audio{ + Mimes: []string{ + "audio/mp4", + }, + MinDuration: 5, + MaxDuration: 30, + Protocols: []int{AudioProtocolDAAST1, AudioProtocolDAAST1Wrapper}, + Sequence: 1, + BAttr: []int{CreativeAttributeUserInitiated, CreativeAttributeWindowsDialogOrAlert}, + MaxExtended: 30, + MinBitrate: 300, + MaxBitrate: 1500, + Delivery: []int{ContentDeliveryProgressive}, + CompanionAd: []Banner{ + {W: 300, H: 250, ID: "1234567893-1", Pos: AdPosAboveFold, BAttr: []int{CreativeAttributeUserInitiated, CreativeAttributeWindowsDialogOrAlert}, ExpDir: []int{ExpDirRight, ExpDirDown}}, + {W: 728, H: 90, ID: "1234567893-2", Pos: AdPosAboveFold, BAttr: []int{CreativeAttributeUserInitiated, CreativeAttributeWindowsDialogOrAlert}}, + }, + API: []int{APIFrameworkVPAID1, APIFrameworkVPAID2}, + CompanionType: []int{VASTCompanionStatic, VASTCompanionHTML}, + } + if got := subject; !reflect.DeepEqual(exp, got) { + t.Errorf("expected %+v, got %+v", exp, got) + } +} -}) +func TestAudio_Validate(t *testing.T) { + subject := &Audio{ + MinDuration: 5, + MaxDuration: 30, + Protocols: []int{AudioProtocolDAAST1, AudioProtocolDAAST1Wrapper}, + Sequence: 1, + BAttr: []int{CreativeAttributeUserInitiated, CreativeAttributeWindowsDialogOrAlert}, + MaxExtended: 30, + MinBitrate: 300, + MaxBitrate: 1500, + Delivery: []int{ContentDeliveryProgressive}, + CompanionAd: []Banner{ + {W: 300, H: 250, ID: "1234567893-1", Pos: AdPosAboveFold, BAttr: []int{CreativeAttributeUserInitiated, CreativeAttributeWindowsDialogOrAlert}, ExpDir: []int{ExpDirRight, ExpDirDown}}, + {W: 728, H: 90, ID: "1234567893-2", Pos: AdPosAboveFold, BAttr: []int{CreativeAttributeUserInitiated, CreativeAttributeWindowsDialogOrAlert}}, + }, + CompanionType: []int{VASTCompanionStatic, VASTCompanionHTML}, + } + if exp, got := ErrInvalidAudioNoMimes, subject.Validate(); !errors.Is(exp, got) { + t.Fatalf("expected %v, got %v", exp, got) + } +} diff --git a/banner.go b/banner.go index 4b34d49..5b6f049 100644 --- a/banner.go +++ b/banner.go @@ -1,6 +1,6 @@ package openrtb -// The "banner" object must be included directly in the impression object if the impression offered +// Banner object must be included directly in the impression object if the impression offered // for auction is display or rich media, or it may be optionally embedded in the video object to // describe the companion banners available for the linear or non-linear video ad. The banner // object may include a unique identifier; this can be useful if these IDs can be leveraged in the diff --git a/banner_test.go b/banner_test.go index a8f8787..8ed96f9 100644 --- a/banner_test.go +++ b/banner_test.go @@ -1,27 +1,28 @@ -package openrtb +package openrtb_test import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" + "reflect" + "testing" + + . "github.com/UnityTech/openrtb" ) -var _ = Describe("Banner", func() { +func TestBanner(t *testing.T) { var subject *Banner + if err := fixture("banner", &subject); err != nil { + t.Fatalf("expected no error, got %v", err) + } - BeforeEach(func() { - err := fixture("banner", &subject) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should parse correctly", func() { - Expect(subject).To(Equal(&Banner{ - W: 728, - H: 90, - Pos: AdPosAboveFold, - BType: []int{BannerTypeFrame}, - BAttr: []int{CreativeAttributeWindowsDialogOrAlert}, - Api: []int{APIFrameworkMRAID1}, - })) - }) - -}) + exp := &Banner{ + W: 728, + H: 90, + Pos: AdPosAboveFold, + BType: []int{BannerTypeFrame}, + BAttr: []int{CreativeAttributeWindowsDialogOrAlert}, + Api: []int{APIFrameworkMRAID1}, + VCM: 1, + } + if got := subject; !reflect.DeepEqual(exp, got) { + t.Errorf("expected %+v, got %+v", exp, got) + } +} diff --git a/bench_test.go b/bench_test.go index c90b2dd..4f20fb2 100644 --- a/bench_test.go +++ b/bench_test.go @@ -2,13 +2,13 @@ package openrtb import ( "encoding/json" - "io/ioutil" + "os" "path/filepath" "testing" ) func BenchmarkBidRequest_Unmarshal(b *testing.B) { - data, err := ioutil.ReadFile(filepath.Join("testdata", "breq.video.json")) + data, err := os.ReadFile(filepath.Join("testdata", "breq.video.json")) if err != nil { b.Fatal(err.Error()) } @@ -23,7 +23,7 @@ func BenchmarkBidRequest_Unmarshal(b *testing.B) { } func BenchmarkBidRequest_Marshal(b *testing.B) { - data, err := ioutil.ReadFile(filepath.Join("testdata", "breq.video.json")) + data, err := os.ReadFile(filepath.Join("testdata", "breq.video.json")) if err != nil { b.Fatal(err.Error()) } diff --git a/bid.go b/bid.go index 3ce69c2..23f7350 100644 --- a/bid.go +++ b/bid.go @@ -8,6 +8,7 @@ var ( ErrInvalidBidNoImpID = errors.New("openrtb: bid is missing impression ID") ) +// Bid object contains bid information. // ID, ImpID and Price are required; all other optional. // If the bidder wins the impression, the exchange calls notice URL (nurl) // a) to inform the bidder of the win; @@ -32,7 +33,7 @@ type Bid struct { Tactic string `json:"tactic,omitempty"` // Tactic ID to enable buyers to label bids for reporting to the exchange the tactic through which their bid was submitted. Cat []string `json:"cat,omitempty"` // IAB content categories of the creative. Refer to List 5.1 Attr []int `json:"attr,omitempty"` // Array of creative attributes. - API int `json:"api,omitempty"` // API required by the markup if applicable + API int `json:"api,omitempty"` // API required by the markup if applicable, NOTE: for ORTB ver <= 2.5 APIFramework supported is 1 to 6. Protocol int `json:"protocol,omitempty"` // Video response protocol of the markup if applicable QAGMediaRating int `json:"qagmediarating,omitempty"` // Creative media rating per IQG guidelines. Language string `json:"language,omitempty"` // Language of the creative using ISO-639-1-alpha-2. @@ -41,11 +42,19 @@ type Bid struct { W int `json:"w,omitempty"` // Width of the ad in pixels. WRatio int `json:"wratio,omitempty"` // Relative width of the creative when expressing size as a ratio. HRatio int `json:"hratio,omitempty"` // Relative height of the creative when expressing size as a ratio. - Exp int `json:"exp,omitempty"` // Advisory as to the number of seconds the bidder is willing to wait between the auction and the actual impression. - ContentType string `json:"-"` // Content of the bid - MediaType string `json:"-"` // Media of the impression e.g. video/display - IsOMEnabled bool `json:"-"` // Flag to send indicate to the Sdk whether or not to run om scripts - Ext Extension `json:"ext,omitempty"` + + APIS []int `json:"apis,omitempty"` // APIS required by the markup if applicable. + LangB string `json:"langb,omitempty"` // Language of the creative using IETF BCP 47. Only one of language or langb should be present. + Duration int `json:"dur,omitempty"` // Duration of the video or audio creative in seconds. + MarkupType MarkupType `json:"mtype,omitempty"` // Creative markup so that it can properly be associated. + SlotInPod SlotPositionInPod `json:"slotinpod,omitempty"` // Indicates that the bid response is only eligible for a specific position. + CategoryTaxonomy CategoryTaxonomy `json:"cattax,omitempty"` // Defines the taxonomy in use. + + Exp int `json:"exp,omitempty"` // Advisory as to the number of seconds the bidder is willing to wait between the auction and the actual impression. + ContentType string `json:"-"` // Content of the bid + MediaType string `json:"-"` // Media of the impression e.g. video/display + IsOMEnabled bool `json:"-"` // Flag to send indicate to the Sdk whether or not to run om scripts + Ext Extension `json:"ext,omitempty"` } // Validate required attributes diff --git a/bid_test.go b/bid_test.go index efdb610..46b4492 100644 --- a/bid_test.go +++ b/bid_test.go @@ -1,76 +1,74 @@ -package openrtb +package openrtb_test import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" + "errors" + "reflect" + "testing" + + . "github.com/UnityTech/openrtb" ) -var _ = Describe("Bid", func() { +func TestBid(t *testing.T) { var subject *Bid + if err := fixture("bid", &subject); err != nil { + t.Fatalf("expected no error, got %v", err) + } - BeforeEach(func() { - err := fixture("bid", &subject) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should parse correctly", func() { - Expect(subject).To(Equal(&Bid{ - ID: "1", - ImpID: "1", - Price: 0.751371, - AdID: "52a5516d29e435137c6f6e74", - NURL: "http://ads.com/win/112770_1386565997?won=${AUCTION_PRICE}", - AdMarkup: "", - AdvDomain: []string{"ads.com"}, - IURL: "http://ads.com/112770_1386565997.jpeg", - CampaignID: "52a5516d29e435137c6f6e74", - CreativeID: "52a5516d29e435137c6f6e74_1386565997", - DealID: "example_deal", - Attr: []int{}, - })) - }) - - It("should validate", func() { - Expect((&Bid{}).Validate()).To(Equal(ErrInvalidBidNoID)) - Expect((&Bid{ID: "BIDID"}).Validate()).To(Equal(ErrInvalidBidNoImpID)) - Expect(subject.Validate()).NotTo(HaveOccurred()) - }) + exp := &Bid{ + ID: "1", + ImpID: "1", + Price: 0.751371, + AdID: "52a5516d29e435137c6f6e74", + NURL: "http://ads.com/win/112770_1386565997?won=${AUCTION_PRICE}", + AdMarkup: "", + AdvDomain: []string{"ads.com"}, + IURL: "http://ads.com/112770_1386565997.jpeg", + CampaignID: "52a5516d29e435137c6f6e74", + CreativeID: "52a5516d29e435137c6f6e74_1386565997", + DealID: "example_deal", + Attr: []int{}, + } + if got := subject; !reflect.DeepEqual(exp, got) { + t.Errorf("expected %+v, got %+v", exp, got) + } +} -}) +func TestBid_Validate(t *testing.T) { + subject := &Bid{} + if exp, got := ErrInvalidBidNoID, subject.Validate(); !errors.Is(exp, got) { + t.Fatalf("expected %v, got %v", exp, got) + } + subject = &Bid{ID: "BIDID"} + if exp, got := ErrInvalidBidNoImpID, subject.Validate(); !errors.Is(exp, got) { + t.Fatalf("expected %v, got %v", exp, got) + } +} -var _ = Describe("Loopme AR Bid", func() { +func TestLoopmeARBid(t *testing.T) { var subject *Bid + if err := fixture("bid.loopme.ar", &subject); err != nil { + t.Fatalf("expected no error, got %v", err) + } - BeforeEach(func() { - err := fixture("bid.loopme.ar", &subject) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should parse correctly", func() { - Expect(subject).To(Equal(&Bid{ - ID: "5d4b3b3be8250678c813f475", - ImpID: "1", - Price: 29.7, - AdID: "2056515", - NURL: "https://us.fake-url.com/tr?et=BID_WIN&a_ecp=29.7&a_price=${AUCTION_PRICE:BF}&name=imp_nurl", - AdMarkup: "", - AdvDomain: []string{"unity.com"}, - IURL: "https://fake-url.net/assets/2690-e3c0-aa99-9efd38cd1fa6.jpg", - CampaignID: "2001626", - CreativeID: "2056515", - Attr: []int{99}, - Protocol: 2, - Cat: []string{"IAB19"}, - LURL: "https://fake-url.com?et=BID_LOSS&meta=&a_id=${AUCTION_ID}&a_bid_id=${AUCTION_BID_ID}&a_loss_id=${AUCTION_LOSS}", - H: 320, - W: 480, - })) - }) - - It("should validate", func() { - Expect((&Bid{}).Validate()).To(Equal(ErrInvalidBidNoID)) - Expect((&Bid{ID: "BIDID"}).Validate()).To(Equal(ErrInvalidBidNoImpID)) - Expect(subject.Validate()).NotTo(HaveOccurred()) - }) - -}) + exp := &Bid{ + ID: "5d4b3b3be8250678c813f475", + ImpID: "1", + Price: 29.7, + AdID: "2056515", + NURL: "https://us.fake-url.com/tr?et=BID_WIN&a_ecp=29.7&a_price=${AUCTION_PRICE:BF}&name=imp_nurl", + AdMarkup: "", + AdvDomain: []string{"unity.com"}, + IURL: "https://fake-url.net/assets/2690-e3c0-aa99-9efd38cd1fa6.jpg", + CampaignID: "2001626", + CreativeID: "2056515", + Attr: []int{99}, + Protocol: 2, + Cat: []string{"IAB19"}, + LURL: "https://fake-url.com?et=BID_LOSS&meta=&a_id=${AUCTION_ID}&a_bid_id=${AUCTION_BID_ID}&a_loss_id=${AUCTION_LOSS}", + H: 320, + W: 480, + } + if got := subject; !reflect.DeepEqual(exp, got) { + t.Errorf("expected %+v, got %+v", exp, got) + } +} diff --git a/bidrequest.go b/bidrequest.go index 3f34695..4c88c39 100644 --- a/bidrequest.go +++ b/bidrequest.go @@ -1,6 +1,8 @@ package openrtb -import "errors" +import ( + "errors" +) // Validation errors var ( @@ -9,7 +11,7 @@ var ( ErrInvalidReqMultiInv = errors.New("openrtb: request has multiple inventory sources") // has site and app ) -// The top-level bid request object contains a globally unique bid request or auction ID. This "id" +// BidRequest is the top-level bid request object contains a globally unique bid request or auction ID. This "id" // attribute is required as is at least one "imp" (i.e., impression) object. Other attributes are // optional since an exchange may establish default values. type BidRequest struct { @@ -24,7 +26,8 @@ type BidRequest struct { TMax int `json:"tmax,omitempty"` // Maximum amount of time in milliseconds to submit a bid WSeat []string `json:"wseat,omitempty"` // Array of buyer seats allowed to bid on this auction BSeat []string `json:"bseat,omitempty"` // Array of buyer seats blocked to bid on this auction - WLang []string `json:"wlang,omitempty"` // Array of languages for creatives using ISO-639-1-alpha-2 + WLang []string `json:"wlang,omitempty"` // Allowed list of languages for creatives using ISO-639-1-alpha-2. Omission implies no specific restrictions, but buyers would be advised to consider language attribute in the Device and/or Content objects if available. Only one of wlang or wlangb should be present. + LanguagesB []string `json:"wlangb,omitempty"` // Allowed list of languages for creatives using IETF BCP 47I. Omission implies no specific restrictions, but buyers would be advised to consider language attribute in the Device and/or Content objects if available. Only one of wlang or wlangb should be present. AllImps int `json:"allimps,omitempty"` // Flag to indicate whether exchange can verify that all impressions offered represent all of the impressions available in context, Default: 0 Cur []string `json:"cur,omitempty"` // Array of allowed currencies Bcat []string `json:"bcat,omitempty"` // Blocked Advertiser Categories. @@ -37,7 +40,7 @@ type BidRequest struct { Pmp *Pmp `json:"pmp,omitempty"` // DEPRECATED: kept for backwards compatibility } -// Validates the request +// Validate the request func (req *BidRequest) Validate() error { if req.ID == "" { return ErrInvalidReqNoID @@ -47,7 +50,8 @@ func (req *BidRequest) Validate() error { return ErrInvalidReqMultiInv } - for _, imp := range req.Imp { + for i := range req.Imp { + imp := req.Imp[i] if err := (&imp).Validate(); err != nil { return err } diff --git a/bidrequest_test.go b/bidrequest_test.go index 68dc085..b2630ba 100644 --- a/bidrequest_test.go +++ b/bidrequest_test.go @@ -1,79 +1,88 @@ -package openrtb +package openrtb_test import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" + "errors" + "reflect" + "testing" + + . "github.com/UnityTech/openrtb" ) -var _ = Describe("BidRequest", func() { +func TestBidRequest(t *testing.T) { var subject *BidRequest - privacyPolicy := 1 - BeforeEach(func() { - err := fixture("breq.banner", &subject) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should parse complex requests", func() { - for _, kind := range []string{"exp", "video", "native"} { - var req *BidRequest - err := fixture("breq."+kind, &req) - Expect(err).NotTo(HaveOccurred(), "for %s", kind) - Expect(req.Validate()).NotTo(HaveOccurred(), "for %s", kind) - } - }) + if err := fixture("breq.banner", &subject); err != nil { + t.Fatalf("expected no error, got %v", err) + } - It("should parse correctly", func() { - Expect(subject).To(Equal(&BidRequest{ - ID: "1234534625254", - Imp: []Impression{ - { - ID: "1", - Secure: 1, - Banner: &Banner{W: 300, H: 250, Pos: AdPosAboveFold, BAttr: []int{CreativeAttributeUserInitiated}}, - }, + privacyPolicy := 1 + exp := &BidRequest{ + ID: "1234534625254", + Imp: []Impression{ + { + ID: "1", + Secure: 1, + Banner: &Banner{W: 300, H: 250, Pos: AdPosAboveFold, BAttr: []int{CreativeAttributeUserInitiated}}, }, - Site: &Site{ - Inventory: Inventory{ - ID: "234563", - Name: "Site ABCD", - Domain: "siteabcd.com", - Cat: []string{"IAB2-1", "IAB2-2"}, - Publisher: &Publisher{ID: "pub12345", Name: "Publisher A"}, - PrivacyPolicy: &privacyPolicy, - Content: &Content{ - Keywords: "keyword a,keyword b,keyword c", - }, + }, + Site: &Site{ + Inventory: Inventory{ + ID: "234563", + Name: "Site ABCD", + Domain: "siteabcd.com", + Cat: []string{string(ContentCategoryAutoParts), string(ContentCategoryAutoRepair)}, + Publisher: &Publisher{ID: "pub12345", Name: "Publisher A"}, + PrivacyPolicy: &privacyPolicy, + Content: &Content{ + Keywords: "keyword a,keyword b,keyword c", }, - Page: "http://siteabcd.com/page.htm", - Ref: "http://referringsite.com/referringpage.htm", }, - Device: &Device{ - UA: "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16", - IP: "64.124.253.1", - OS: "OS X", - JS: 1, - FlashVer: "10.1", - }, - User: &User{ - ID: "45asdf987656789adfad4678rew656789", - BuyerUID: "5df678asd8987656asdf78987654", - }, - Test: 1, - AuctionType: 2, - TMax: 120, - BAdv: []string{"company1.com", "company2.com"}, - })) - }) - - It("should validate", func() { - Expect((&BidRequest{}).Validate()).To(Equal(ErrInvalidReqNoID)) - Expect((&BidRequest{ID: "A"}).Validate()).To(Equal(ErrInvalidReqNoImps)) - Expect((&BidRequest{ID: "A", Imp: []Impression{{ID: "1"}}, Site: &Site{}, App: &App{}}).Validate()).To(Equal(ErrInvalidReqMultiInv)) + Page: "http://siteabcd.com/page.htm", + Ref: "http://referringsite.com/referringpage.htm", + }, + Device: &Device{ + UA: "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16", + IP: "64.124.253.1", + OS: "OS X", + JS: 1, + FlashVer: "10.1", + }, + User: &User{ + ID: "45asdf987656789adfad4678rew656789", + BuyerUID: "5df678asd8987656asdf78987654", + }, + Test: 1, + AuctionType: 2, + TMax: 120, + BAdv: []string{"company1.com", "company2.com"}, + } + if got := subject; !reflect.DeepEqual(exp, got) { + t.Errorf("expected %+v, got %+v", exp, got) + } +} - Expect((&BidRequest{ID: "A", Imp: []Impression{{ID: "1", Banner: &Banner{}}}}).Validate()).NotTo(HaveOccurred()) - Expect((&BidRequest{ID: "A", Imp: []Impression{{ID: "1", Banner: &Banner{}}}, Site: &Site{}}).Validate()).NotTo(HaveOccurred()) - Expect((&BidRequest{ID: "A", Imp: []Impression{{ID: "1", Banner: &Banner{}}}, App: &App{}}).Validate()).NotTo(HaveOccurred()) - Expect(subject.Validate()).NotTo(HaveOccurred()) - }) +func TestBidRequest_complex(t *testing.T) { + for _, kind := range []string{"exp", "video", "native"} { + var subject *BidRequest + if err := fixture("breq."+kind, &subject); err != nil { + t.Fatalf("expected no error, got %v", err) + } + if err := subject.Validate(); err != nil { + t.Fatalf("expected no error, got %v", err) + } + } +} -}) +func TestBidRequest_Validate(t *testing.T) { + subject := &BidRequest{} + if exp, got := ErrInvalidReqNoID, subject.Validate(); !errors.Is(exp, got) { + t.Fatalf("expected %v, got %v", exp, got) + } + subject = &BidRequest{ID: "RID"} + if exp, got := ErrInvalidReqNoImps, subject.Validate(); !errors.Is(exp, got) { + t.Fatalf("expected %v, got %v", exp, got) + } + subject = &BidRequest{ID: "A", Imp: []Impression{{ID: "1"}}, Site: &Site{}, App: &App{}} + if exp, got := ErrInvalidReqMultiInv, subject.Validate(); !errors.Is(exp, got) { + t.Fatalf("expected %v, got %v", exp, got) + } +} diff --git a/bidresponse.go b/bidresponse.go index a759e2a..345fbb4 100644 --- a/bidresponse.go +++ b/bidresponse.go @@ -1,6 +1,8 @@ package openrtb -import "errors" +import ( + "errors" +) // Validation errors var ( @@ -8,6 +10,7 @@ var ( ErrInvalidRespNoSeatBids = errors.New("openrtb: response missing seatbids") ) +// BidResponse is the bid response wrapper object. // ID and at least one "seatbid” object is required, which contains a bid on at least one impression. // Other attributes are optional since an exchange may establish default values. // No-Bids on all impressions should be indicated as a HTTP 204 response. @@ -30,11 +33,11 @@ func (res *BidResponse) Validate() error { return ErrInvalidRespNoSeatBids } - for _, sb := range res.SeatBid { - if err := sb.Validate(); err != nil { + for i := range res.SeatBid { + sb := res.SeatBid[i] + if err := (&sb).Validate(); err != nil { return err } } - return nil } diff --git a/bidresponse_test.go b/bidresponse_test.go index 8f81f17..d858f57 100644 --- a/bidresponse_test.go +++ b/bidresponse_test.go @@ -1,57 +1,66 @@ -package openrtb +package openrtb_test import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" + "errors" + "reflect" + "testing" + + . "github.com/UnityTech/openrtb" ) -var _ = Describe("BidResponse", func() { +func TestBidResponse(t *testing.T) { var subject *BidResponse + if err := fixture("bres.single", &subject); err != nil { + t.Fatalf("expected no error, got %v", err) + } - BeforeEach(func() { - err := fixture("bres.single", &subject) - Expect(err).NotTo(HaveOccurred()) - }) - - It("should parse complex responses", func() { - for _, kind := range []string{"multi", "pmp", "vast"} { - var req *BidResponse - err := fixture("bres."+kind, &req) - Expect(err).NotTo(HaveOccurred(), "for %s", kind) - Expect(req.Validate()).NotTo(HaveOccurred(), "for %s", kind) - } - }) - - It("should parse responses", func() { - Expect(subject).To(Equal(&BidResponse{ - ID: "BID-4-ZIMP-4b309eae-504a-4252-a8a8-4c8ceee9791a", - SeatBid: []SeatBid{ - { - Bid: []Bid{ - { - ID: "32a69c6ba388f110487f9d1e63f77b22d86e916b", - ImpID: "32a69c6ba388f110487f9d1e63f77b22d86e916b", - Price: 0.065445, - AdID: "529833ce55314b19e8796116", - NURL: "http://ads.com/win/529833ce55314b19e8796116?won=${auction_price}", - AdMarkup: "