From 62ce92d8126355ffaa97d2b52c8af6828f5a590a Mon Sep 17 00:00:00 2001 From: Dimitrij Denissenko Date: Mon, 28 Oct 2019 11:37:04 +0000 Subject: [PATCH] New major release --- .travis.yml | 10 +- LICENSE | 24 + README.md | 42 +- audio.go | 48 +- audio_test.go | 61 ++- banner.go | 36 +- banner_test.go | 17 +- bid.go | 55 +- bid_test.go | 11 +- bidrequest.go | 51 +- bidrequest_test.go | 40 +- bidresponse.go | 13 +- bidresponse_test.go | 17 +- content.go | 54 +- content_test.go | 10 +- device.go | 64 +-- device_test.go | 19 +- doc.go | 2 +- go.mod | 15 +- go.sum | 31 +- impression.go | 36 +- impression_test.go | 9 +- inventory.go | 37 +- inventory_test.go | 22 +- native.go | 12 +- native/request/asset.go | 2 +- native/request/data.go | 3 + native/request/image.go | 5 +- native/request/request.go | 58 ++- native/request/request_test.go | 8 +- native/request/title.go | 1 + native/request/video.go | 18 +- native/response/asset.go | 2 +- native/response/data.go | 1 + native/response/image.go | 1 + native/response/link.go | 1 + native/response/response.go | 6 +- native/response/response_test.go | 13 +- native/response/title.go | 1 + native/response/video.go | 1 + native_test.go | 6 +- numbers_test.go | 4 - openrtb.go | 851 +++++++++++++++++++++++++------ openrtb_test.go | 3 +- pmp.go | 13 +- pmp_test.go | 13 +- seatbid.go | 11 +- seatbid_test.go | 4 +- video.go | 62 +-- video_test.go | 63 ++- 50 files changed, 1196 insertions(+), 691 deletions(-) create mode 100644 LICENSE diff --git a/.travis.yml b/.travis.yml index 55a9f71..b888a14 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,9 @@ language: go go: - - 1.10.x - - 1.11.x -install: - - go get -t ./... + - 1.12.x + - 1.13.x env: - GO111MODULE=on - +cache: + directories: + - $GOPATH/pkg/mod 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 e127471..73cb6be 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,29 @@ -# Go OpenRTB v2.x +# OpenRTB [![Build Status](https://travis-ci.org/bsm/openrtb.svg?branch=master)](https://travis-ci.org/bsm/openrtb) -OpenRTB implementation for Go +OpenRTB structs and validations for Go. ## Requirements -Requires Go 1.8+ for proper `json.RawMessage` marshalling. +Requires Go 1.8+ for proper `json.RawMessage` marshaling. ## Installation To install, use `go get`: ```shell -go get github.com/bsm/openrtb +go get github.com/bsm/openrtb/v3 ``` ## Usage -Import the package: - ```go package main import ( "log" - "github.com/bsm/openrtb" + "github.com/bsm/openrtb/v3" ) func main() { @@ -36,38 +34,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 4a64e5a..1c67131 100644 --- a/audio.go +++ b/audio.go @@ -7,38 +7,38 @@ import ( // Validation errors var ( - ErrInvalidAudioNoMimes = errors.New("openrtb: audio has no mimes") + 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 - MaxDuration int `json:"maxduration,omitempty"` // Maximum video ad duration in seconds - Protocols []int `json:"protocols,omitempty"` // Video bid response protocols - StartDelay int `json:"startdelay,omitempty"` // Indicates the start delay in seconds - Sequence int `json:"sequence,omitempty"` // Default: 1 - BAttr []int `json:"battr,omitempty"` // Blocked creative attributes - MaxExtended int `json:"maxextended,omitempty"` // Maximum extended video ad duration - MinBitrate int `json:"minbitrate,omitempty"` // Minimum bit rate in Kbps - MaxBitrate int `json:"maxbitrate,omitempty"` // Maximum bit rate in Kbps - Delivery []int `json:"delivery,omitempty"` // List of supported delivery methods - CompanionAd []Banner `json:"companionad,omitempty"` - API []int `json:"api,omitempty"` - CompanionType []int `json:"companiontype,omitempty"` - MaxSequence int `json:"maxseq,omitempty"` // The maximumnumber of ads that canbe played in an ad pod. - Feed int `json:"feed,omitempty"` // Type of audio feed. - Stitched int `json:"stitched,omitempty"` // Indicates if the ad is stitched with audio content or delivered independently - NVol int `json:"nvol,omitempty"` // Volume normalization mode. - Ext json.RawMessage `json:"ext,omitempty"` + MIMEs []string `json:"mimes"` // Content MIME types supported. + MinDuration int `json:"minduration,omitempty"` // Minimum video ad duration in seconds + MaxDuration int `json:"maxduration,omitempty"` // Maximum video ad duration in seconds + Protocols []Protocol `json:"protocols,omitempty"` // Video bid response protocols + StartDelay StartDelay `json:"startdelay,omitempty"` // Indicates the start delay in seconds + Sequence int `json:"sequence,omitempty"` // Default: 1 + BlockedAttrs []CreativeAttribute `json:"battr,omitempty"` // Blocked creative attributes + MaxExtended int `json:"maxextended,omitempty"` // Maximum extended video ad duration + MinBitrate int `json:"minbitrate,omitempty"` // Minimum bit rate in Kbps + MaxBitrate int `json:"maxbitrate,omitempty"` // Maximum bit rate in Kbps + Delivery []ContentDelivery `json:"delivery,omitempty"` // List of supported delivery methods + CompanionAds []Banner `json:"companionad,omitempty"` + APIs []APIFramework `json:"api,omitempty"` + CompanionTypes []CompanionType `json:"companiontype,omitempty"` + MaxSequence int `json:"maxseq,omitempty"` // The maximumnumber of ads that canbe played in an ad pod. + Feed FeedType `json:"feed,omitempty"` // Type of audio feed. + Stitched int `json:"stitched,omitempty"` // Indicates if the ad is stitched with audio content or delivered independently + VolumeNorm VolumeNorm `json:"nvol,omitempty"` // Volume normalization mode. + Ext json.RawMessage `json:"ext,omitempty"` } type jsonAudio Audio -// Validates the object +// Validate the object func (a *Audio) Validate() error { - if len(a.Mimes) == 0 { - return ErrInvalidAudioNoMimes + if len(a.MIMEs) == 0 { + return ErrInvalidAudioNoMIMEs } return nil } diff --git a/audio_test.go b/audio_test.go index 4a4937e..85e9dfd 100644 --- a/audio_test.go +++ b/audio_test.go @@ -9,50 +9,49 @@ var _ = Describe("Audio", func() { var subject *Audio BeforeEach(func() { - err := fixture("audio", &subject) - Expect(err).NotTo(HaveOccurred()) + Expect(fixture("audio", &subject)).To(Succeed()) }) It("should parse correctly", func() { Expect(subject).To(Equal(&Audio{ - Mimes: []string{ + 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}}, + MinDuration: 5, + MaxDuration: 30, + Protocols: []Protocol{ProtocolDAAST1, ProtocolDAAST1Wrapper}, + Sequence: 1, + BlockedAttrs: []CreativeAttribute{CreativeAttributeUserInitiated, CreativeAttributeWindowsDialogOrAlert}, + MaxExtended: 30, + MinBitrate: 300, + MaxBitrate: 1500, + Delivery: []ContentDelivery{ContentDeliveryProgressive}, + CompanionAds: []Banner{ + {Width: 300, Height: 250, ID: "1234567893-1", Position: AdPositionAboveFold, BlockedAttrs: []CreativeAttribute{CreativeAttributeUserInitiated, CreativeAttributeWindowsDialogOrAlert}, ExpDirs: []ExpDir{ExpDirRight, ExpDirDown}}, + {Width: 728, Height: 90, ID: "1234567893-2", Position: AdPositionAboveFold, BlockedAttrs: []CreativeAttribute{CreativeAttributeUserInitiated, CreativeAttributeWindowsDialogOrAlert}}, }, - API: []int{APIFrameworkVPAID1, APIFrameworkVPAID2}, - CompanionType: []int{VASTCompanionStatic, VASTCompanionHTML}, + APIs: []APIFramework{APIFrameworkVPAID1, APIFrameworkVPAID2}, + CompanionTypes: []CompanionType{CompanionTypeStatic, CompanionTypeHTML}, })) }) 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}}, + MinDuration: 5, + MaxDuration: 30, + Protocols: []Protocol{ProtocolDAAST1, ProtocolDAAST1Wrapper}, + Sequence: 1, + BlockedAttrs: []CreativeAttribute{CreativeAttributeUserInitiated, CreativeAttributeWindowsDialogOrAlert}, + MaxExtended: 30, + MinBitrate: 300, + MaxBitrate: 1500, + Delivery: []ContentDelivery{ContentDeliveryProgressive}, + CompanionAds: []Banner{ + {Width: 300, Height: 250, ID: "1234567893-1", Position: AdPositionAboveFold, BlockedAttrs: []CreativeAttribute{CreativeAttributeUserInitiated, CreativeAttributeWindowsDialogOrAlert}, ExpDirs: []ExpDir{ExpDirRight, ExpDirDown}}, + {Width: 728, Height: 90, ID: "1234567893-2", Position: AdPositionAboveFold, BlockedAttrs: []CreativeAttribute{CreativeAttributeUserInitiated, CreativeAttributeWindowsDialogOrAlert}}, }, - CompanionType: []int{VASTCompanionStatic, VASTCompanionHTML}, - }).Validate()).To(Equal(ErrInvalidAudioNoMimes)) + CompanionTypes: []CompanionType{CompanionTypeStatic, CompanionTypeHTML}, + }).Validate()).To(Equal(ErrInvalidAudioNoMIMEs)) }) }) diff --git a/banner.go b/banner.go index 5468626..0b8fc98 100644 --- a/banner.go +++ b/banner.go @@ -2,28 +2,28 @@ package openrtb import "encoding/json" -// 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 // VAST response to dictate placement of the companion creatives when multiple companion ad // opportunities of the same size are available on a page. type Banner struct { - W int `json:"w,omitempty"` // Width - H int `json:"h,omitempty"` // Height - Format []Format `json:"format,omitempty"` //Array of format objects representing the banner sizes permitted. - WMax int `json:"wmax,omitempty"` // Width maximum DEPRECATED - HMax int `json:"hmax,omitempty"` // Height maximum DEPRECATED - WMin int `json:"wmin,omitempty"` // Width minimum DEPRECATED - HMin int `json:"hmin,omitempty"` // Height minimum DEPRECATED - ID string `json:"id,omitempty"` // A unique identifier - BType []int `json:"btype,omitempty"` // Blocked creative types - BAttr []int `json:"battr,omitempty"` // Blocked creative attributes - Pos int `json:"pos,omitempty"` // Ad Position - Mimes []string `json:"mimes,omitempty"` // Whitelist of content MIME types supported - TopFrame int `json:"topframe,omitempty"` // Default: 0 ("1": Delivered in top frame, "0": Elsewhere) - ExpDir []int `json:"expdir,omitempty"` // Specify properties for an expandable ad - Api []int `json:"api,omitempty"` // List of supported API frameworks - Vcm int `json:"vcm,omitempty"` // Represents the relationship with video. 0 = concurrent, 1 = end-card - Ext json.RawMessage `json:"ext,omitempty"` + Width int `json:"w,omitempty"` // Width + Height int `json:"h,omitempty"` // Height + Formats []Format `json:"format,omitempty"` // Array of format objects representing the banner sizes permitted. + WidthMax int `json:"wmax,omitempty"` // Width maximum DEPRECATED + HeightMax int `json:"hmax,omitempty"` // Height maximum DEPRECATED + WidthMin int `json:"wmin,omitempty"` // Width minimum DEPRECATED + HeightMin int `json:"hmin,omitempty"` // Height minimum DEPRECATED + ID string `json:"id,omitempty"` // A unique identifier + BlockedTypes []BannerType `json:"btype,omitempty"` // Blocked banner types + BlockedAttrs []CreativeAttribute `json:"battr,omitempty"` // Blocked creative attributes + Position AdPosition `json:"pos,omitempty"` // Ad Position + MIMEs []string `json:"mimes,omitempty"` // Whitelist of content MIME types supported + TopFrame int `json:"topframe,omitempty"` // Default: 0 ("1": Delivered in top frame, "0": Elsewhere) + ExpDirs []ExpDir `json:"expdir,omitempty"` // Specify properties for an expandable ad + APIs []APIFramework `json:"api,omitempty"` // List of supported API frameworks + VCM int `json:"vcm,omitempty"` // Represents the relationship with video. 0 = concurrent, 1 = end-card + Ext json.RawMessage `json:"ext,omitempty"` } diff --git a/banner_test.go b/banner_test.go index f982d2b..26e3f0d 100644 --- a/banner_test.go +++ b/banner_test.go @@ -9,19 +9,18 @@ var _ = Describe("Banner", func() { var subject *Banner BeforeEach(func() { - err := fixture("banner", &subject) - Expect(err).NotTo(HaveOccurred()) + Expect(fixture("banner", &subject)).To(Succeed()) }) 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}, - Vcm: 1, + Width: 728, + Height: 90, + Position: AdPositionAboveFold, + BlockedTypes: []BannerType{BannerTypeFrame}, + BlockedAttrs: []CreativeAttribute{CreativeAttributeWindowsDialogOrAlert}, + APIs: []APIFramework{APIFrameworkMRAID1}, + VCM: 1, })) }) diff --git a/bid.go b/bid.go index 18a98c8..f8a3a5c 100644 --- a/bid.go +++ b/bid.go @@ -11,6 +11,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; @@ -19,33 +20,33 @@ var ( // Cid can be used to block ads that were previously identified as inappropriate. // Substitution macros may allow a bidder to use a static notice URL for all of its bids. type Bid struct { - ID string `json:"id"` - ImpID string `json:"impid"` // Required string ID of the impression object to which this bid applies. - Price float64 `json:"price"` // Bid price in CPM. Suggests using integer math for accounting to avoid rounding errors. - AdID string `json:"adid,omitempty"` // References the ad to be served if the bid wins. - NURL string `json:"nurl,omitempty"` // Win notice URL. - BURL string `json:"burl,omitempty"` // Billing notice URL. - LURL string `json:"lurl,omitempty"` // Loss notice URL. - AdMarkup string `json:"adm,omitempty"` // Actual ad markup. XHTML if a response to a banner object, or VAST XML if a response to a video object. - AdvDomain []string `json:"adomain,omitempty"` // Advertiser’s primary or top-level domain for advertiser checking; or multiple if imp rotating. - Bundle string `json:"bundle,omitempty"` // A platform-specific application identifier intended to be unique to the app and independent of the exchange. - IURL string `json:"iurl,omitempty"` // Sample image URL. - CampaignID StringOrNumber `json:"cid,omitempty"` // Campaign ID that appears with the Ad markup. - CreativeID string `json:"crid,omitempty"` // Creative ID for reporting content issues or defects. This could also be used as a reference to a creative ID that is posted with an exchange. - 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 - 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. - DealID string `json:"dealid,omitempty"` // DealID extension of private marketplace deals - H int `json:"h,omitempty"` // Height of the ad in pixels. - 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. - Ext json.RawMessage `json:"ext,omitempty"` + ID string `json:"id"` + ImpID string `json:"impid"` // Required string ID of the impression object to which this bid applies. + Price float64 `json:"price"` // Bid price in CPM. Suggests using integer math for accounting to avoid rounding errors. + AdID string `json:"adid,omitempty"` // References the ad to be served if the bid wins. + NoticeURL string `json:"nurl,omitempty"` // Win notice URL. + BillingURL string `json:"burl,omitempty"` // Billing notice URL. + LossURL string `json:"lurl,omitempty"` // Loss notice URL. + AdMarkup string `json:"adm,omitempty"` // Actual ad markup. XHTML if a response to a banner object, or VAST XML if a response to a video object. + AdvDomains []string `json:"adomain,omitempty"` // Advertiser’s primary or top-level domain for advertiser checking; or multiple if imp rotating. + Bundle string `json:"bundle,omitempty"` // A platform-specific application identifier intended to be unique to the app and independent of the exchange. + ImageURL string `json:"iurl,omitempty"` // Sample image URL. + CampaignID StringOrNumber `json:"cid,omitempty"` // Campaign ID that appears with the Ad markup. + CreativeID string `json:"crid,omitempty"` // Creative ID for reporting content issues or defects. This could also be used as a reference to a creative ID that is posted with an exchange. + 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. + Categories []ContentCategory `json:"cat,omitempty"` // IAB content categories of the creative. Refer to List 5.1 + Attrs []CreativeAttribute `json:"attr,omitempty"` // Array of creative attributes. + API APIFramework `json:"api,omitempty"` // API required by the markup if applicable + Protocol Protocol `json:"protocol,omitempty"` // Video response protocol of the markup if applicable + MediaRating IQGRating `json:"qagmediarating,omitempty"` // Creative media rating per IQG guidelines. + Language string `json:"language,omitempty"` // Language of the creative using ISO-639-1-alpha-2. + DealID string `json:"dealid,omitempty"` // DealID extension of private marketplace deals + Width int `json:"w,omitempty"` // Width of the ad in pixels. + Height int `json:"h,omitempty"` // Height of the ad in pixels. + WidthRatio int `json:"wratio,omitempty"` // Relative width of the creative when expressing size as a ratio. + HeightRatio 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. + Ext json.RawMessage `json:"ext,omitempty"` } // Validate required attributes diff --git a/bid_test.go b/bid_test.go index 369d2bd..44899b3 100644 --- a/bid_test.go +++ b/bid_test.go @@ -9,8 +9,7 @@ var _ = Describe("Bid", func() { var subject *Bid BeforeEach(func() { - err := fixture("bid", &subject) - Expect(err).NotTo(HaveOccurred()) + Expect(fixture("bid", &subject)).To(Succeed()) }) It("should parse correctly", func() { @@ -19,14 +18,14 @@ var _ = Describe("Bid", func() { ImpID: "1", Price: 0.751371, AdID: "52a5516d29e435137c6f6e74", - NURL: "http://ads.com/win/112770_1386565997?won=${AUCTION_PRICE}", + NoticeURL: "http://ads.com/win/112770_1386565997?won=${AUCTION_PRICE}", AdMarkup: "", - AdvDomain: []string{"ads.com"}, - IURL: "http://ads.com/112770_1386565997.jpeg", + AdvDomains: []string{"ads.com"}, + ImageURL: "http://ads.com/112770_1386565997.jpeg", CampaignID: "52a5516d29e435137c6f6e74", CreativeID: "52a5516d29e435137c6f6e74_1386565997", DealID: "example_deal", - Attr: []int{}, + Attrs: []CreativeAttribute{}, })) }) diff --git a/bidrequest.go b/bidrequest.go index 41fbd0e..d8d6873 100644 --- a/bidrequest.go +++ b/bidrequest.go @@ -12,45 +12,44 @@ 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 { - ID string `json:"id"` // Unique ID of the bid request - Imp []Impression `json:"imp,omitempty"` - Site *Site `json:"site,omitempty"` - App *App `json:"app,omitempty"` - Device *Device `json:"device,omitempty"` - User *User `json:"user,omitempty"` - Test int `json:"test,omitempty"` // Indicator of test mode in which auctions are not billable, where 0 = live mode, 1 = test mode - AuctionType int `json:"at"` // Auction type, where 1 = First Price, 2 = Second Price Plus. Exchange-specific auction types can be defined using values greater than 500. - 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 - 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. - BAdv []string `json:"badv,omitempty"` // Array of strings of blocked toplevel domains of advertisers - BApp []string `json:"bapp,omitempty"` // Block list of applications by their platform-specific exchange-independent application identifiers. On Android, these should be bundle or package names (e.g., com.foo.mygame). On iOS, these are numeric IDs. - Source *Source `json:"source,omitempty"` // A Source object that provides data about the inventory source and which entity makes the final decision - Regs *Regulations `json:"regs,omitempty"` - Ext json.RawMessage `json:"ext,omitempty"` - - Pmp *Pmp `json:"pmp,omitempty"` // DEPRECATED: kept for backwards compatibility + ID string `json:"id"` // Unique ID of the bid request + Impressions []Impression `json:"imp,omitempty"` + Site *Site `json:"site,omitempty"` + App *App `json:"app,omitempty"` + Device *Device `json:"device,omitempty"` + User *User `json:"user,omitempty"` + Test int `json:"test,omitempty"` // Indicator of test mode in which auctions are not billable, where 0 = live mode, 1 = test mode + AuctionType int `json:"at"` // Auction type, where 1 = First Price, 2 = Second Price Plus. Exchange-specific auction types can be defined using values greater than 500. + TimeMax int `json:"tmax,omitempty"` // Maximum amount of time in milliseconds to submit a bid + Seats []string `json:"wseat,omitempty"` // Array of buyer seats allowed to bid on this auction + BlockedSeats []string `json:"bseat,omitempty"` // Array of buyer seats blocked to bid on this auction + Languages []string `json:"wlang,omitempty"` // Array of languages for creatives using ISO-639-1-alpha-2 + AllImpressions 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 + Currencies []string `json:"cur,omitempty"` // Array of allowed currencies + BlockedCategories []ContentCategory `json:"bcat,omitempty"` // Blocked Advertiser Categories. + BlockedAdvDomains []string `json:"badv,omitempty"` // Array of strings of blocked toplevel domains of advertisers + BlockedApps []string `json:"bapp,omitempty"` // Block list of applications by their platform-specific exchange-independent application identifiers. On Android, these should be bundle or package names (e.g., com.foo.mygame). On iOS, these are numeric IDs. + Source *Source `json:"source,omitempty"` // A Source object that provides data about the inventory source and which entity makes the final decision + Regulations *Regulations `json:"regs,omitempty"` + Ext json.RawMessage `json:"ext,omitempty"` } -// Validates the request +// Validate the request func (req *BidRequest) Validate() error { if req.ID == "" { return ErrInvalidReqNoID - } else if len(req.Imp) == 0 { + } else if len(req.Impressions) == 0 { return ErrInvalidReqNoImps } else if req.Site != nil && req.App != nil { return ErrInvalidReqMultiInv } - for _, imp := range req.Imp { + for i := range req.Impressions { + imp := req.Impressions[i] if err := (&imp).Validate(); err != nil { return err } diff --git a/bidrequest_test.go b/bidrequest_test.go index 68dc085..88131c3 100644 --- a/bidrequest_test.go +++ b/bidrequest_test.go @@ -8,9 +8,9 @@ import ( var _ = Describe("BidRequest", func() { var subject *BidRequest privacyPolicy := 1 + BeforeEach(func() { - err := fixture("breq.banner", &subject) - Expect(err).NotTo(HaveOccurred()) + Expect(fixture("breq.banner", &subject)).To(Succeed()) }) It("should parse complex requests", func() { @@ -25,11 +25,11 @@ var _ = Describe("BidRequest", func() { It("should parse correctly", func() { Expect(subject).To(Equal(&BidRequest{ ID: "1234534625254", - Imp: []Impression{ + Impressions: []Impression{ { ID: "1", Secure: 1, - Banner: &Banner{W: 300, H: 250, Pos: AdPosAboveFold, BAttr: []int{CreativeAttributeUserInitiated}}, + Banner: &Banner{Width: 300, Height: 250, Position: AdPositionAboveFold, BlockedAttrs: []CreativeAttribute{CreativeAttributeUserInitiated}}, }, }, Site: &Site{ @@ -37,42 +37,42 @@ var _ = Describe("BidRequest", func() { ID: "234563", Name: "Site ABCD", Domain: "siteabcd.com", - Cat: []string{"IAB2-1", "IAB2-2"}, + Categories: []ContentCategory{ContentCategoryAutoParts, 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", + Page: "http://siteabcd.com/page.htm", + Referrer: "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", + 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, + FlashVersion: "10.1", }, User: &User{ ID: "45asdf987656789adfad4678rew656789", BuyerUID: "5df678asd8987656asdf78987654", }, - Test: 1, - AuctionType: 2, - TMax: 120, - BAdv: []string{"company1.com", "company2.com"}, + Test: 1, + AuctionType: 2, + TimeMax: 120, + BlockedAdvDomains: []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)) + Expect((&BidRequest{ID: "A", Impressions: []Impression{{ID: "1"}}, Site: &Site{}, App: &App{}}).Validate()).To(Equal(ErrInvalidReqMultiInv)) - 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((&BidRequest{ID: "A", Impressions: []Impression{{ID: "1", Banner: &Banner{}}}}).Validate()).NotTo(HaveOccurred()) + Expect((&BidRequest{ID: "A", Impressions: []Impression{{ID: "1", Banner: &Banner{}}}, Site: &Site{}}).Validate()).NotTo(HaveOccurred()) + Expect((&BidRequest{ID: "A", Impressions: []Impression{{ID: "1", Banner: &Banner{}}}, App: &App{}}).Validate()).NotTo(HaveOccurred()) Expect(subject.Validate()).NotTo(HaveOccurred()) }) diff --git a/bidresponse.go b/bidresponse.go index 31d07af..58dde72 100644 --- a/bidresponse.go +++ b/bidresponse.go @@ -11,17 +11,18 @@ 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. // For no-bids on specific impressions, the bidder should omit these from the bid response. type BidResponse struct { ID string `json:"id"` // Reflection of the bid request ID for logging purposes - SeatBid []SeatBid `json:"seatbid"` // Array of seatbid objects + SeatBids []SeatBid `json:"seatbid"` // Array of seatbid objects BidID string `json:"bidid,omitempty"` // Optional response tracking ID for bidders Currency string `json:"cur,omitempty"` // Bid currency CustomData string `json:"customdata,omitempty"` // Encoded user features - NBR int `json:"nbr,omitempty"` // Reason for not bidding, where 0 = unknown error, 1 = technical error, 2 = invalid request, 3 = known web spider, 4 = suspected Non-Human Traffic, 5 = cloud, data center, or proxy IP, 6 = unsupported device, 7 = blocked publisher or site, 8 = unmatched user + NBR NBR `json:"nbr,omitempty"` // Reason for not bidding, where 0 = unknown error, 1 = technical error, 2 = invalid request, 3 = known web spider, 4 = suspected Non-Human Traffic, 5 = cloud, data center, or proxy IP, 6 = unsupported device, 7 = blocked publisher or site, 8 = unmatched user Ext json.RawMessage `json:"ext,omitempty"` // Custom specifications in JSon } @@ -29,15 +30,15 @@ type BidResponse struct { func (res *BidResponse) Validate() error { if res.ID == "" { return ErrInvalidRespNoID - } else if len(res.SeatBid) == 0 { + } else if len(res.SeatBids) == 0 { return ErrInvalidRespNoSeatBids } - for _, sb := range res.SeatBid { - if err := sb.Validate(); err != nil { + for i := range res.SeatBids { + sb := res.SeatBids[i] + if err := (&sb).Validate(); err != nil { return err } } - return nil } diff --git a/bidresponse_test.go b/bidresponse_test.go index 8f81f17..3b94222 100644 --- a/bidresponse_test.go +++ b/bidresponse_test.go @@ -9,15 +9,13 @@ var _ = Describe("BidResponse", func() { var subject *BidResponse BeforeEach(func() { - err := fixture("bres.single", &subject) - Expect(err).NotTo(HaveOccurred()) + Expect(fixture("bres.single", &subject)).To(Succeed()) }) 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(fixture("bres."+kind, &req)).To(Succeed(), "for %s", kind) Expect(req.Validate()).NotTo(HaveOccurred(), "for %s", kind) } }) @@ -25,20 +23,20 @@ var _ = Describe("BidResponse", func() { It("should parse responses", func() { Expect(subject).To(Equal(&BidResponse{ ID: "BID-4-ZIMP-4b309eae-504a-4252-a8a8-4c8ceee9791a", - SeatBid: []SeatBid{ + SeatBids: []SeatBid{ { - Bid: []Bid{ + Bids: []Bid{ { ID: "32a69c6ba388f110487f9d1e63f77b22d86e916b", ImpID: "32a69c6ba388f110487f9d1e63f77b22d86e916b", Price: 0.065445, AdID: "529833ce55314b19e8796116", - NURL: "http://ads.com/win/529833ce55314b19e8796116?won=${auction_price}", + NoticeURL: "http://ads.com/win/529833ce55314b19e8796116?won=${auction_price}", AdMarkup: "