From f81d1f499491c2c1e05938632a08dbed72f23cfb Mon Sep 17 00:00:00 2001 From: Scott Kay Date: Thu, 10 Sep 2020 13:26:43 -0400 Subject: [PATCH] Pass Through First Party Context Data (#1479) --- endpoints/openrtb2/auction.go | 16 +- endpoints/openrtb2/auction_test.go | 95 +++++++++- ...valid-context-allowed-with-ext-bidder.json | 32 ++++ ...id-context-allowed-with-prebid-bidder.json | 36 ++++ ...rstpartydata-imp-ext-multiple-bidders.json | 173 +++++++++++++++++ ...ydata-imp-ext-multiple-prebid-bidders.json | 179 ++++++++++++++++++ .../firstpartydata-imp-ext-one-bidder.json | 103 ++++++++++ ...stpartydata-imp-ext-one-prebid-bidder.json | 108 +++++++++++ exchange/utils.go | 38 ++-- openrtb_ext/bidders.go | 3 + openrtb_ext/bidders_test.go | 5 + openrtb_ext/request.go | 4 + 12 files changed, 769 insertions(+), 23 deletions(-) create mode 100644 endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-ext-bidder.json create mode 100644 endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-prebid-bidder.json create mode 100644 exchange/exchangetest/firstpartydata-imp-ext-multiple-bidders.json create mode 100644 exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json create mode 100644 exchange/exchangetest/firstpartydata-imp-ext-one-bidder.json create mode 100644 exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index bc0cd90073f..41c1c1677a5 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -765,8 +765,8 @@ func (deps *endpointDeps) validateImpExt(imp *openrtb.Imp, aliases map[string]st } // Also accept bidder exts within imp[...].ext.prebid.bidder - // NOTE: This is not part of the official API, we are not expecting clients - // migrate from imp[...].ext.${BIDDER} to imp[...].ext.prebid.bidder.${BIDDER} + // NOTE: This is not part of the official API yet, so we are not expecting clients + // to migrate from imp[...].ext.${BIDDER} to imp[...].ext.prebid.bidder.${BIDDER} // at this time // https://github.com/prebid/prebid-server/pull/846#issuecomment-476352224 if rawPrebidExt, ok := bidderExts[openrtb_ext.PrebidExtKey]; ok { @@ -785,7 +785,7 @@ func (deps *endpointDeps) validateImpExt(imp *openrtb.Imp, aliases map[string]st /* Process all the bidder exts in the request */ disabledBidders := []string{} for bidder, ext := range bidderExts { - if bidder != openrtb_ext.PrebidExtKey { + if isBidderToValidate(bidder) { coreBidder := bidder if tmp, isAlias := aliases[bidder]; isAlias { coreBidder = tmp @@ -820,12 +820,20 @@ func (deps *endpointDeps) validateImpExt(imp *openrtb.Imp, aliases map[string]st // TODO #713 Fix this here if len(bidderExts) < 1 { errL = append(errL, fmt.Errorf("request.imp[%d].ext must contain at least one bidder", impIndex)) - return errL } return errL } +func isBidderToValidate(bidder string) bool { + // PrebidExtKey is a special case for the prebid config section and is not considered a bidder. + + // FirstPartyDataContextExtKey is a special case for the first party data context section + // and is not considered a bidder. + + return bidder != openrtb_ext.PrebidExtKey && bidder != openrtb_ext.FirstPartyDataContextExtKey +} + func (deps *endpointDeps) parseBidExt(ext json.RawMessage) (*openrtb_ext.ExtRequest, error) { if len(ext) < 1 { return nil, nil diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 53fea2e0500..7dc244a28c3 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -141,6 +141,16 @@ func TestGoodRequests(t *testing.T) { supplementary.assert(t) } +func TestFirstPartyDataRequests(t *testing.T) { + validRequests := &getResponseFromDirectory{ + dir: "sample-requests/first-party-data", + payloadGetter: getRequestPayload, + messageGetter: nilReturner, + expectedCode: http.StatusOK, + } + validRequests.assert(t) +} + // TestGoodNativeRequests makes sure we return 200s on well-formed Native requests. func TestGoodNativeRequests(t *testing.T) { tests := &getResponseFromDirectory{ @@ -1127,10 +1137,73 @@ func TestDisabledBidder(t *testing.T) { } } -func TestValidateImpExtDisabledBidder(t *testing.T) { - imp := &openrtb.Imp{ - Ext: json.RawMessage(`{"appnexus":{"placement_id":555},"unknownbidder":{"foo":"bar"}}`), +func TestValidateImpExt(t *testing.T) { + testCases := []struct { + description string + impExt json.RawMessage + expectedImpExt string + expectedErrs []error + }{ + { + description: "Empty", + impExt: nil, + expectedImpExt: "", + expectedErrs: []error{errors.New("request.imp[0].ext is required")}, + }, + { + description: "Valid Bidder", + impExt: json.RawMessage(`{"appnexus":{"placement_id":555}}`), + expectedImpExt: `{"appnexus":{"placement_id":555}}`, + expectedErrs: []error{}, + }, + { + description: "Valid Bidder + Disabled Bidder", + impExt: json.RawMessage(`{"appnexus":{"placement_id":555},"unknownbidder":{"foo":"bar"}}`), + expectedImpExt: `{"appnexus":{"placement_id":555}}`, + expectedErrs: []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'unknownbidder' has been disabled."}}, + }, + { + description: "Valid Bidder + Disabled Bidder + First Party Data Context", + impExt: json.RawMessage(`{"appnexus":{"placement_id":555},"unknownbidder":{"foo":"bar"},"context":{"data":{"keywords":"prebid server example"}}}`), + expectedImpExt: `{"appnexus":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`, + expectedErrs: []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'unknownbidder' has been disabled."}}, + }, + { + description: "Valid Bidder + First Party Data Context", + impExt: json.RawMessage(`{"appnexus":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`), + expectedImpExt: `{"appnexus":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`, + expectedErrs: []error{}, + }, + { + description: "Valid Prebid Ext Bidder", + impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`), + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`, + expectedErrs: []error{}, + // request.imp[x].ext.prebid.bidder.{biddername} is only promoted/copied to request.ext.{biddername} if there is at least one disabled bidder. + }, + { + description: "Valid Prebid Ext Bidder + First Party Data Context", + impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555}}} ,"context":{"data":{"keywords":"prebid server example"}}}`), + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`, + expectedErrs: []error{}, + // request.imp[x].ext.prebid.bidder.{biddername} is only promoted/copied to request.ext.{biddername} if there is at least one disabled bidder. + }, + { + description: "Valid Prebid Ext Bidder + Disabled Bidder", + impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555},"unknownbidder":{"foo":"bar"}}}}`), + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id": 555},"unknownbidder":{"foo":"bar"}}},"appnexus":{"placement_id":555}}`, + expectedErrs: []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'unknownbidder' has been disabled."}}, + // request.imp[x].ext.prebid.bidder.{biddername} disabled bidders are not removed. if there is a disabled bidder, the valid ones are promoted/copied to request.ext.{biddername}. + }, + { + description: "Valid Prebid Ext Bidder + Disabled Bidder + First Party Data Context", + impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555},"unknownbidder":{"foo":"bar"}}},"context":{"data":{"keywords":"prebid server example"}}}`), + expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id": 555},"unknownbidder":{"foo":"bar"}}},"appnexus":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`, + expectedErrs: []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'unknownbidder' has been disabled."}}, + // request.imp[x].ext.prebid.bidder.{biddername} disabled bidders are not removed. if there is a disabled bidder, the valid ones are promoted/copied to request.ext.{biddername}. + }, } + deps := &endpointDeps{ &nobidExchange{}, newParamsValidator(t), @@ -1149,9 +1222,19 @@ func TestValidateImpExtDisabledBidder(t *testing.T) { nil, hardcodedResponseIPValidator{response: true}, } - errs := deps.validateImpExt(imp, nil, 0) - assert.JSONEq(t, `{"appnexus":{"placement_id":555}}`, string(imp.Ext)) - assert.Equal(t, []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'unknownbidder' has been disabled."}}, errs) + + for _, test := range testCases { + imp := &openrtb.Imp{Ext: test.impExt} + + errs := deps.validateImpExt(imp, nil, 0) + + if len(test.expectedImpExt) > 0 { + assert.JSONEq(t, test.expectedImpExt, string(imp.Ext)) + } else { + assert.Empty(t, imp.Ext) + } + assert.Equal(t, test.expectedErrs, errs) + } } func validRequest(t *testing.T, filename string) string { diff --git a/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-ext-bidder.json b/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-ext-bidder.json new file mode 100644 index 00000000000..aa205fc55ce --- /dev/null +++ b/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-ext-bidder.json @@ -0,0 +1,32 @@ +{ + "description": "The imp.ext.context field is valid for First Party Data and should be exempted from bidder name validation.", + + "requestPayload": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + }, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + } +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-prebid-bidder.json b/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-prebid-bidder.json new file mode 100644 index 00000000000..1616e84b416 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-prebid-bidder.json @@ -0,0 +1,36 @@ +{ + "description": "The imp.ext.context field is valid for First Party Data and should be exempted from bidder name validation.", + + "requestPayload": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + } + } + }, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + } +} \ No newline at end of file diff --git a/exchange/exchangetest/firstpartydata-imp-ext-multiple-bidders.json b/exchange/exchangetest/firstpartydata-imp-ext-multiple-bidders.json new file mode 100644 index 00000000000..8004c3c2646 --- /dev/null +++ b/exchange/exchangetest/firstpartydata-imp-ext-multiple-bidders.json @@ -0,0 +1,173 @@ +{ + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "appnexus": { + "placementId": 1 + }, + "rubicon": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + }, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "bidder": { + "placementId": 1 + }, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + }, + "bidAdjustment": 1.0 + }, + "mockResponse": { + "pbsSeatBid": { + "pbsBids": [{ + "ortbBid": { + "id": "apn-bid", + "impid": "some-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-1" + }, + "bidType": "banner" + }] + } + } + }, + "rubicon": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "bidder": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + }, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + }, + "bidAdjustment": 1.0 + }, + "mockResponse": { + "pbsSeatBid": { + "pbsBids": [{ + "ortbBid": { + "id": "rubi-bid", + "impid": "some-imp-id", + "price": 0.4, + "w": 200, + "h": 500, + "crid": "creative-2" + }, + "bidType": "banner" + }] + } + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [{ + "seat": "appnexus", + "bid": [{ + "id": "apn-bid", + "impid": "some-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-1", + "ext": { + "prebid": { + "type": "banner" + } + } + }] + }, { + "seat": "rubicon", + "bid": [{ + "id": "rubi-bid", + "impid": "some-imp-id", + "price": 0.4, + "w": 200, + "h": 500, + "crid": "creative-2", + "ext": { + "prebid": { + "type": "banner" + } + } + }] + }] + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json b/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json new file mode 100644 index 00000000000..d62afccf426 --- /dev/null +++ b/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json @@ -0,0 +1,179 @@ +{ + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + }, + "rubicon": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + } + } + }, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "bidder": { + "placementId": 1 + }, + "prebid": {}, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + }, + "bidAdjustment": 1.0 + }, + "mockResponse": { + "pbsSeatBid": { + "pbsBids": [{ + "ortbBid": { + "id": "apn-bid", + "impid": "some-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-1" + }, + "bidType": "banner" + }] + } + } + }, + "rubicon": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "bidder": { + "accountId": 1, + "siteId": 2, + "zoneId": 3 + }, + "prebid": {}, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + }, + "bidAdjustment": 1.0 + }, + "mockResponse": { + "pbsSeatBid": { + "pbsBids": [{ + "ortbBid": { + "id": "rubi-bid", + "impid": "some-imp-id", + "price": 0.4, + "w": 200, + "h": 500, + "crid": "creative-2" + }, + "bidType": "banner" + }] + } + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [{ + "seat": "appnexus", + "bid": [{ + "id": "apn-bid", + "impid": "some-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-1", + "ext": { + "prebid": { + "type": "banner" + } + } + }] + }, { + "seat": "rubicon", + "bid": [{ + "id": "rubi-bid", + "impid": "some-imp-id", + "price": 0.4, + "w": 200, + "h": 500, + "crid": "creative-2", + "ext": { + "prebid": { + "type": "banner" + } + } + }] + }] + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/firstpartydata-imp-ext-one-bidder.json b/exchange/exchangetest/firstpartydata-imp-ext-one-bidder.json new file mode 100644 index 00000000000..6f0bab9529c --- /dev/null +++ b/exchange/exchangetest/firstpartydata-imp-ext-one-bidder.json @@ -0,0 +1,103 @@ +{ + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "appnexus": { + "placementId": 1 + }, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "bidder": { + "placementId": 1 + }, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + }, + "bidAdjustment": 1.0 + }, + "mockResponse": { + "pbsSeatBid": { + "pbsBids": [{ + "ortbBid": { + "id": "apn-bid", + "impid": "some-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-1" + }, + "bidType": "banner" + }] + } + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [{ + "seat": "appnexus", + "bid": [{ + "id": "apn-bid", + "impid": "some-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-1", + "ext": { + "prebid": { + "type": "banner" + } + } + }] + }] + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json b/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json new file mode 100644 index 00000000000..1610b9ea47e --- /dev/null +++ b/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json @@ -0,0 +1,108 @@ +{ + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 1 + } + } + }, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + } + }, + "outgoingRequests": { + "appnexus": { + "expectRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "bidder": { + "placementId": 1 + }, + "prebid": {}, + "context": { + "data": { + "keywords": "prebid server example" + } + } + } + }] + }, + "bidAdjustment": 1.0 + }, + "mockResponse": { + "pbsSeatBid": { + "pbsBids": [{ + "ortbBid": { + "id": "apn-bid", + "impid": "some-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-1" + }, + "bidType": "banner" + }] + } + } + } + }, + "response": { + "bids": { + "id": "some-request-id", + "seatbid": [{ + "seat": "appnexus", + "bid": [{ + "id": "apn-bid", + "impid": "some-imp-id", + "price": 0.3, + "w": 200, + "h": 500, + "crid": "creative-1", + "ext": { + "prebid": { + "type": "banner" + } + } + }] + }] + } + } +} \ No newline at end of file diff --git a/exchange/utils.go b/exchange/utils.go index 2e9e4dc8f80..5863f6c8530 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -273,13 +273,18 @@ func splitImps(imps []openrtb.Imp) (map[string][]openrtb.Imp, []error) { imp := imps[i] impExt := impExts[i] + var firstPartyDataContext json.RawMessage + if context, exists := impExt[openrtb_ext.FirstPartyDataContextExtKey]; exists { + firstPartyDataContext = context + } + rawPrebidExt, ok := impExt[openrtb_ext.PrebidExtKey] if ok { var prebidExt openrtb_ext.ExtImpPrebid if err := json.Unmarshal(rawPrebidExt, &prebidExt); err == nil && prebidExt.Bidder != nil { - if errs := sanitizedImpCopy(&imp, prebidExt.Bidder, rawPrebidExt, &splitImps); errs != nil { + if errs := sanitizedImpCopy(&imp, prebidExt.Bidder, rawPrebidExt, firstPartyDataContext, &splitImps); errs != nil { errList = append(errList, errs...) } @@ -287,7 +292,7 @@ func splitImps(imps []openrtb.Imp) (map[string][]openrtb.Imp, []error) { } } - if errs := sanitizedImpCopy(&imp, impExt, rawPrebidExt, &splitImps); errs != nil { + if errs := sanitizedImpCopy(&imp, impExt, rawPrebidExt, firstPartyDataContext, &splitImps); errs != nil { errList = append(errList, errs...) } } @@ -295,35 +300,38 @@ func splitImps(imps []openrtb.Imp) (map[string][]openrtb.Imp, []error) { return splitImps, nil } -// sanitizedImpCopy returns a copy of imp with its ext filtered so that only "prebid" and bidder params exist. +// sanitizedImpCopy returns a copy of imp with its ext filtered so that only "prebid", "context", and bidder params exist. // It will not mutate the input imp. // This function will write the new imps to the output map passed in func sanitizedImpCopy(imp *openrtb.Imp, bidderExts map[string]json.RawMessage, rawPrebidExt json.RawMessage, + firstPartyDataContext json.RawMessage, out *map[string][]openrtb.Imp) []error { var prebidExt map[string]json.RawMessage var errs []error - // We don't want to include other demand partners' bidder params - // in the sanitized imp if err := json.Unmarshal(rawPrebidExt, &prebidExt); err == nil { - delete(prebidExt, "bidder") - - var err error - if rawPrebidExt, err = json.Marshal(prebidExt); err != nil { - errs = append(errs, err) + // Remove the entire bidder field. We will already have the content we need in bidderExts. We + // don't want to include other demand partners' bidder params in the sanitized imp. + if _, hasBidderField := prebidExt["bidder"]; hasBidderField { + delete(prebidExt, "bidder") + + var err error + if rawPrebidExt, err = json.Marshal(prebidExt); err != nil { + errs = append(errs, err) + } } } for bidder, ext := range bidderExts { - if bidder == openrtb_ext.PrebidExtKey { + if bidder == openrtb_ext.PrebidExtKey || bidder == openrtb_ext.FirstPartyDataContextExtKey { continue } impCopy := *imp - newExt := make(map[string]json.RawMessage, 2) + newExt := make(map[string]json.RawMessage, 3) newExt["bidder"] = ext @@ -331,6 +339,10 @@ func sanitizedImpCopy(imp *openrtb.Imp, newExt[openrtb_ext.PrebidExtKey] = rawPrebidExt } + if len(firstPartyDataContext) > 0 { + newExt[openrtb_ext.FirstPartyDataContextExtKey] = firstPartyDataContext + } + rawExt, err := json.Marshal(newExt) if err != nil { errs = append(errs, err) @@ -392,7 +404,7 @@ func resolveBidder(bidder string, aliases map[string]string) openrtb_ext.BidderN } // parseImpExts does a partial-unmarshal of the imp[].Ext field. -// The keys in the returned map are expected to be "prebid", core BidderNames, or Aliases for this request. +// The keys in the returned map are expected to be "prebid", "context", core BidderNames, or Aliases for this request. func parseImpExts(imps []openrtb.Imp) ([]map[string]json.RawMessage, error) { exts := make([]map[string]json.RawMessage, len(imps)) // Loop over every impression in the request diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 761f53d441e..876eeab86bd 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -20,6 +20,9 @@ type BidderName string // BidderNameGeneral is reserved for non-bidder specific messages when using a map keyed on the bidder name. const BidderNameGeneral = BidderName("general") +// BidderNameContext is reserved for first party data. +const BidderNameContext = BidderName("context") + // These names _must_ coincide with the bidder code in Prebid.js, if an adapter also exists in that project. // Please keep these (and the BidderMap) alphabetized to minimize merge conflicts among adapter submissions. // The bidder name 'general' is not allowed since it has special meaning in message maps. diff --git a/openrtb_ext/bidders_test.go b/openrtb_ext/bidders_test.go index d49b23237ed..9f05f526905 100644 --- a/openrtb_ext/bidders_test.go +++ b/openrtb_ext/bidders_test.go @@ -61,3 +61,8 @@ func TestBidderListDoesNotDefineGeneral(t *testing.T) { bidders := BidderList() assert.NotContains(t, bidders, BidderNameGeneral) } + +func TestBidderListDoesNotDefineContext(t *testing.T) { + bidders := BidderList() + assert.NotContains(t, bidders, BidderNameContext) +} diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index d6edf47f939..42ac9d9d4b9 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -5,6 +5,10 @@ import ( "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"`