Skip to content

Commit

Permalink
Multi-bid support (#2468)
Browse files Browse the repository at this point in the history
  • Loading branch information
pm-nilesh-chate authored Mar 7, 2023
1 parent 9145b07 commit b77c77f
Show file tree
Hide file tree
Showing 22 changed files with 4,485 additions and 376 deletions.
1 change: 1 addition & 0 deletions config/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type Account struct {
Hooks AccountHooks `mapstructure:"hooks" json:"hooks"`
PriceFloors AccountPriceFloors `mapstructure:"price_floors" json:"price_floors"`
Validations Validations `mapstructure:"validations" json:"validations"`
DefaultBidLimit int `mapstructure:"default_bid_limit" json:"default_bid_limit"`
}

// CookieSync represents the account-level defaults for the cookie sync endpoint.
Expand Down
31 changes: 23 additions & 8 deletions endpoints/openrtb2/auction.go
Original file line number Diff line number Diff line change
Expand Up @@ -716,8 +716,8 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp
return append(errL, errors.New("request.site or request.app must be defined, but not both."))
}

if err := validateRequestExt(req); err != nil {
return append(errL, err)
if errs := validateRequestExt(req); len(errs) != 0 {
return append(errL, errs...)
}

if err := deps.validateSite(req); err != nil {
Expand Down Expand Up @@ -1497,30 +1497,45 @@ func (deps *endpointDeps) validateAliasesGVLIDs(aliasesGVLIDs map[string]uint16,
return nil
}

func validateRequestExt(req *openrtb_ext.RequestWrapper) error {
func validateRequestExt(req *openrtb_ext.RequestWrapper) []error {
reqExt, err := req.GetRequestExt()
if err != nil {
return err
return []error{err}
}

prebid := reqExt.GetPrebid()

// exit early if there is no request.ext.prebid to validate
if prebid == nil {
return nil
}

if prebid.Cache != nil {
if prebid.Cache.Bids == nil && prebid.Cache.VastXML == nil {
return errors.New(`request.ext is invalid: request.ext.prebid.cache requires one of the "bids" or "vastxml" properties`)
return []error{errors.New(`request.ext is invalid: request.ext.prebid.cache requires one of the "bids" or "vastxml" properties`)}
}
}

if err := validateTargeting(prebid.Targeting); err != nil {
return err
return []error{err}
}

return nil
var errs []error
if prebid.MultiBid != nil {
validatedMultiBids, multBidErrs := openrtb_ext.ValidateAndBuildExtMultiBid(prebid)

for _, err := range multBidErrs {
errs = append(errs, &errortypes.Warning{
WarningCode: errortypes.MultiBidWarningCode,
Message: err.Error(),
})
}

// update the downstream multibid to avoid passing unvalidated ext to bidders, etc.
prebid.MultiBid = validatedMultiBids
reqExt.SetPrebid(prebid)
}

return errs
}

func validateTargeting(t *openrtb_ext.ExtRequestTargeting) error {
Expand Down
121 changes: 109 additions & 12 deletions endpoints/openrtb2/auction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1631,7 +1631,7 @@ func TestValidateRequestExt(t *testing.T) {
testCases := []struct {
description string
givenRequestExt json.RawMessage
expectedError string
expectedErrors []string
}{
{
description: "nil",
Expand All @@ -1648,31 +1648,31 @@ func TestValidateRequestExt(t *testing.T) {
{
description: "prebid cache - empty",
givenRequestExt: json.RawMessage(`{"prebid":{"cache":{}}}`),
expectedError: `request.ext is invalid: request.ext.prebid.cache requires one of the "bids" or "vastxml" properties`,
expectedErrors: []string{`request.ext is invalid: request.ext.prebid.cache requires one of the "bids" or "vastxml" properties`},
},
{
description: "prebid cache - bids - null",
givenRequestExt: json.RawMessage(`{"prebid":{"cache":{"bids":null}}}`),
expectedError: `request.ext is invalid: request.ext.prebid.cache requires one of the "bids" or "vastxml" properties`,
expectedErrors: []string{`request.ext is invalid: request.ext.prebid.cache requires one of the "bids" or "vastxml" properties`},
},
{
description: "prebid cache - bids - wrong type",
givenRequestExt: json.RawMessage(`{"prebid":{"cache":{"bids":true}}}`),
expectedError: `json: cannot unmarshal bool into Go struct field ExtRequestPrebidCache.cache.bids of type openrtb_ext.ExtRequestPrebidCacheBids`,
expectedErrors: []string{`json: cannot unmarshal bool into Go struct field ExtRequestPrebidCache.cache.bids of type openrtb_ext.ExtRequestPrebidCacheBids`},
},
{
description: "prebid cache - bids - provided",
givenRequestExt: json.RawMessage(`{"prebid":{"cache":{"bids":{}}}}`),
},
{
description: "prebid cache - vastxml - null",
givenRequestExt: json.RawMessage(`{"prebid":{"cache":{"vastxml":null}}}`),
expectedError: `request.ext is invalid: request.ext.prebid.cache requires one of the "bids" or "vastxml" properties`,
givenRequestExt: json.RawMessage(`{"prebid": {"cache": {"vastxml": null}}}`),
expectedErrors: []string{`request.ext is invalid: request.ext.prebid.cache requires one of the "bids" or "vastxml" properties`},
},
{
description: "prebid cache - vastxml - wrong type",
givenRequestExt: json.RawMessage(`{"prebid":{"cache":{"vastxml":true}}}`),
expectedError: `json: cannot unmarshal bool into Go struct field ExtRequestPrebidCache.cache.vastxml of type openrtb_ext.ExtRequestPrebidCacheVAST`,
expectedErrors: []string{`json: cannot unmarshal bool into Go struct field ExtRequestPrebidCache.cache.vastxml of type openrtb_ext.ExtRequestPrebidCacheVAST`},
},
{
description: "prebid cache - vastxml - provided",
Expand All @@ -1685,18 +1685,32 @@ func TestValidateRequestExt(t *testing.T) {
{
description: "prebid targeting", // test integration with validateTargeting
givenRequestExt: json.RawMessage(`{"prebid":{"targeting":{}}}`),
expectedError: "ext.prebid.targeting: At least one of includewinners or includebidderkeys must be enabled to enable targeting support",
expectedErrors: []string{"ext.prebid.targeting: At least one of includewinners or includebidderkeys must be enabled to enable targeting support"},
},
{
description: "valid multibid",
givenRequestExt: json.RawMessage(`{"prebid": {"multibid": [{"Bidder": "pubmatic", "MaxBids": 2}]}}`),
},
{
description: "multibid with invalid entries",
givenRequestExt: json.RawMessage(`{"prebid": {"multibid": [{"Bidder": "pubmatic"}, {"Bidder": "pubmatic", "MaxBids": 2}, {"Bidders": ["pubmatic"], "MaxBids": 3}]}}`),
expectedErrors: []string{
`maxBids not defined for {Bidder:pubmatic, Bidders:[], MaxBids:<nil>, TargetBidderCodePrefix:}`,
`multiBid already defined for pubmatic, ignoring this instance {Bidder:, Bidders:[pubmatic], MaxBids:3, TargetBidderCodePrefix:}`,
},
},
}

for _, test := range testCases {
w := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: test.givenRequestExt}}
err := validateRequestExt(w)
errs := validateRequestExt(w)

if len(test.expectedError) > 0 {
assert.EqualError(t, err, test.expectedError, test.description)
if len(test.expectedErrors) > 0 {
for i, expectedError := range test.expectedErrors {
assert.EqualError(t, errs[i], expectedError, test.description)
}
} else {
assert.NoError(t, err, test.description)
assert.Nil(t, errs, test.description)
}
}
}
Expand Down Expand Up @@ -5116,6 +5130,89 @@ func TestSendAuctionResponse_LogsErrors(t *testing.T) {
}
}

func TestParseRequestMultiBid(t *testing.T) {
tests := []struct {
name string
givenRequestBody string
expectedReqExt json.RawMessage
expectedErrors []error
}{
{
name: "validate and build multi-bid extension",
givenRequestBody: validRequest(t, "multi-bid-error.json"),
expectedReqExt: getObject(t, "multi-bid-error.json", "expectedReqExt"),
expectedErrors: []error{
&errortypes.Warning{
WarningCode: errortypes.MultiBidWarningCode,
Message: "maxBids not defined for {Bidder:appnexus, Bidders:[], MaxBids:<nil>, TargetBidderCodePrefix:}",
},
&errortypes.Warning{
WarningCode: errortypes.MultiBidWarningCode,
Message: "invalid maxBids value, using minimum 1 limit for {Bidder:rubicon, Bidders:[], MaxBids:-1, TargetBidderCodePrefix:rubN}",
},
&errortypes.Warning{
WarningCode: errortypes.MultiBidWarningCode,
Message: "invalid maxBids value, using maximum 9 limit for {Bidder:pubmatic, Bidders:[], MaxBids:10, TargetBidderCodePrefix:pm}",
},
&errortypes.Warning{
WarningCode: errortypes.MultiBidWarningCode,
Message: "multiBid already defined for pubmatic, ignoring this instance {Bidder:pubmatic, Bidders:[], MaxBids:4, TargetBidderCodePrefix:pubM}",
},
&errortypes.Warning{
WarningCode: errortypes.MultiBidWarningCode,
Message: "ignoring bidders from {Bidder:groupm, Bidders:[someBidder], MaxBids:5, TargetBidderCodePrefix:gm}",
},
&errortypes.Warning{
WarningCode: errortypes.MultiBidWarningCode,
Message: "multiBid already defined for groupm, ignoring this instance {Bidder:, Bidders:[groupm], MaxBids:6, TargetBidderCodePrefix:}",
},
&errortypes.Warning{
WarningCode: errortypes.MultiBidWarningCode,
Message: "ignoring targetbiddercodeprefix for {Bidder:, Bidders:[33across], MaxBids:7, TargetBidderCodePrefix:abc}",
},
&errortypes.Warning{
WarningCode: errortypes.MultiBidWarningCode,
Message: "bidder(s) not specified for {Bidder:, Bidders:[], MaxBids:8, TargetBidderCodePrefix:xyz}",
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
deps := &endpointDeps{
fakeUUIDGenerator{},
&warningsCheckExchange{},
mockBidderParamValidator{},
&mockStoredReqFetcher{},
empty_fetcher.EmptyFetcher{},
empty_fetcher.EmptyFetcher{},
&config.Configuration{MaxRequestSize: int64(len(test.givenRequestBody))},
&metricsConfig.NilMetricsEngine{},
analyticsConf.NewPBSAnalytics(&config.Analytics{}),
map[string]string{},
false,
[]byte{},
openrtb_ext.BuildBidderMap(),
nil,
nil,
hardcodedResponseIPValidator{response: true},
empty_fetcher.EmptyFetcher{},
hookexecution.NewHookExecutor(hooks.EmptyPlanBuilder{}, hookexecution.EndpointAuction, &metricsConfig.NilMetricsEngine{}),
}

req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.givenRequestBody))

resReq, _, _, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{})

assert.NoError(t, resReq.RebuildRequest())

assert.JSONEq(t, string(test.expectedReqExt), string(resReq.Ext))

assert.Equal(t, errL, test.expectedErrors, "error length should match")
})
}
}

type mockStoredResponseFetcher struct {
data map[string]json.RawMessage
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
{
"description": "Well formed amp request with valid Site field",
"mockBidRequest": {
"id": "some-request-id",
"site": {
"page": "test.somepage.com"
},
"imp": [
{
"id": "my-imp-id",
"banner": {
"format": [
{
"w": 300,
"h": 600
}
]
},
"pmp": {
"deals": [
{
"id": "some-deal-id"
}
]
},
"ext": {
"appnexus": {
"placementId": 12883451
}
}
}
],
"ext": {
"prebid": {
"multibid": [
{
"bidder": "appnexus"
},
{
"maxbids": -1,
"bidder": "rubicon",
"targetbiddercodeprefix": "rubN"
},
{
"maxbids": 10,
"bidder": "pubmatic",
"targetbiddercodeprefix": "pm"
},
{
"maxbids": 4,
"bidder": "pubmatic",
"targetbiddercodeprefix": "pubM"
},
{
"maxbids": 5,
"bidder": "groupm",
"bidders": [
"someBidder"
],
"targetbiddercodeprefix": "gm"
},
{
"maxbids": 6,
"bidders": [
"groupm"
]
},
{
"maxbids": 7,
"bidders": [
"33across"
],
"targetbiddercodeprefix": "abc"
},
{
"maxbids": 8,
"targetbiddercodeprefix": "xyz"
}
]
}
}
},
"expectedReqExt": {
"prebid": {
"multibid": [
{
"maxbids": 1,
"bidder": "rubicon",
"targetbiddercodeprefix": "rubN"
},
{
"maxbids": 9,
"bidder": "pubmatic",
"targetbiddercodeprefix": "pm"
},
{
"maxbids": 5,
"bidder": "groupm",
"targetbiddercodeprefix": "gm"
},
{
"maxbids": 7,
"bidders": [
"33across"
]
}
]
}
},
"expectedReturnCode": 200
}
1 change: 1 addition & 0 deletions errortypes/code.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const (
BidderLevelDebugDisabledWarningCode
DisabledCurrencyConversionWarningCode
AlternateBidderCodeWarningCode
MultiBidWarningCode
)

// Coder provides an error or warning code with severity.
Expand Down
Loading

0 comments on commit b77c77f

Please sign in to comment.