Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ORTB 2.4 schain support #2108

Merged
merged 10 commits into from
Jan 13, 2022
33 changes: 32 additions & 1 deletion endpoints/openrtb2/auction.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"github.com/prebid/prebid-server/prebid_cache_client"
"github.com/prebid/prebid-server/privacy/ccpa"
"github.com/prebid/prebid-server/privacy/lmt"
"github.com/prebid/prebid-server/schain"
"github.com/prebid/prebid-server/stored_requests"
"github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher"
"github.com/prebid/prebid-server/usersync"
Expand Down Expand Up @@ -502,6 +503,10 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp
}
}

if err := mapSChains(req); err != nil {
return []error{err}
}

if err := validateOrFillChannel(req, isAmp); err != nil {
return []error{err}
}
Expand Down Expand Up @@ -566,6 +571,32 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp
return errL
}

// mapSChains maps an schain defined in an ORTB 2.4 location (req.ext.schain) to the ORTB 2.5 location
// (req.source.ext.schain) if no ORTB 2.5 schain (req.source.ext.schain, req.ext.prebid.schains) exists.
// An ORTB 2.4 schain is always deleted from the 2.4 location regardless of whether an ORTB 2.5 schain exists.
func mapSChains(req *openrtb_ext.RequestWrapper) error {
reqExt, err := req.GetRequestExt()
if err != nil {
return fmt.Errorf("req.ext is invalid: %v", err)
}
sourceExt, err := req.GetSourceExt()
if err != nil {
return fmt.Errorf("source.ext is invalid: %v", err)
}

reqExtSChain := reqExt.GetSChain()
reqExt.SetSChain(nil)

if reqPrebid := reqExt.GetPrebid(); reqPrebid != nil && reqPrebid.SChains != nil {
return nil
} else if sourceExt.GetSChain() != nil {
return nil
} else if reqExtSChain != nil {
sourceExt.SetSChain(reqExtSChain)
}
return nil
}

func validateAndFillSourceTID(req *openrtb2.BidRequest) error {
if req.Source == nil {
req.Source = &openrtb2.Source{}
Expand Down Expand Up @@ -595,7 +626,7 @@ func (deps *endpointDeps) validateBidAdjustmentFactors(adjustmentFactors map[str
}

func validateSChains(sChains []*openrtb_ext.ExtRequestPrebidSChain) error {
_, err := exchange.BidderToPrebidSChains(sChains)
_, err := schain.BidderToPrebidSChains(sChains)
return err
}

Expand Down
128 changes: 128 additions & 0 deletions endpoints/openrtb2/auction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2134,6 +2134,134 @@ func TestSChainInvalid(t *testing.T) {
assert.ElementsMatch(t, errL, []error{expectedError})
}

func TestMapSChains(t *testing.T) {
const seller1SChain string = `"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}`
const seller2SChain string = `"schain":{"complete":2,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":2}],"ver":"2.0"}`

seller1SChainUnpacked := openrtb_ext.ExtRequestPrebidSChainSChain{
Complete: 1,
Nodes: []*openrtb_ext.ExtRequestPrebidSChainSChainNode{{
ASI: "directseller1.com",
SID: "00001",
RID: "BidRequest1",
HP: 1,
}},
Ver: "1.0",
}

tests := []struct {
description string
bidRequest openrtb2.BidRequest
wantReqExtSChain *openrtb_ext.ExtRequestPrebidSChainSChain
wantSourceExtSChain *openrtb_ext.ExtRequestPrebidSChainSChain
wantError bool
}{
{
description: "invalid req.ext",
bidRequest: openrtb2.BidRequest{
Ext: json.RawMessage(`{"prebid":{"schains":invalid}}`),
Source: &openrtb2.Source{
Ext: json.RawMessage(`{}`),
},
},
wantError: true,
},
{
description: "invalid source.ext",
bidRequest: openrtb2.BidRequest{
Ext: json.RawMessage(`{}`),
Source: &openrtb2.Source{
Ext: json.RawMessage(`{"schain":invalid}}`),
},
},
wantError: true,
},
{
description: "req.ext.prebid.schains, req.source.ext.schain and req.ext.schain are nil",
bidRequest: openrtb2.BidRequest{
Ext: json.RawMessage(`{}`),
Source: &openrtb2.Source{
Ext: json.RawMessage(`{}`),
},
},
wantReqExtSChain: nil,
wantSourceExtSChain: nil,
},
{
description: "req.ext.prebid.schains is not nil",
bidRequest: openrtb2.BidRequest{
Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`),
Source: &openrtb2.Source{
Ext: json.RawMessage(`{}`),
},
},
wantReqExtSChain: nil,
wantSourceExtSChain: nil,
},
{
description: "req.source.ext is not nil",
bidRequest: openrtb2.BidRequest{
Ext: json.RawMessage(`{}`),
Source: &openrtb2.Source{
Ext: json.RawMessage(`{` + seller1SChain + `}`),
},
},
wantReqExtSChain: nil,
wantSourceExtSChain: &seller1SChainUnpacked,
},
{
description: "req.ext.schain is not nil",
bidRequest: openrtb2.BidRequest{
Ext: json.RawMessage(`{` + seller1SChain + `}`),
Source: &openrtb2.Source{
Ext: json.RawMessage(`{}`),
},
},
wantReqExtSChain: nil,
wantSourceExtSChain: &seller1SChainUnpacked,
},
{
description: "req.source.ext.schain and req.ext.schain are not nil",
bidRequest: openrtb2.BidRequest{
Ext: json.RawMessage(`{` + seller2SChain + `}`),
Source: &openrtb2.Source{
Ext: json.RawMessage(`{` + seller1SChain + `}`),
},
},
wantReqExtSChain: nil,
wantSourceExtSChain: &seller1SChainUnpacked,
},
}

for _, tt := range tests {
reqWrapper := openrtb_ext.RequestWrapper{
BidRequest: &tt.bidRequest,
}

err := mapSChains(&reqWrapper)

if tt.wantError {
assert.NotNil(t, err, tt.description)
} else {
assert.Nil(t, err, tt.description)

reqExt, err := reqWrapper.GetRequestExt()
if err != nil {
assert.Fail(t, "Error getting request ext from wrapper", tt.description)
}
reqExtSChain := reqExt.GetSChain()
assert.Equal(t, tt.wantReqExtSChain, reqExtSChain, tt.description)

sourceExt, err := reqWrapper.GetSourceExt()
if err != nil {
assert.Fail(t, "Error getting source ext from wrapper", tt.description)
}
sourceExtSChain := sourceExt.GetSChain()
assert.Equal(t, tt.wantSourceExtSChain, sourceExtSChain, tt.description)
}
}
}

func TestGetAccountID(t *testing.T) {
testPubID := "test-pub"
testParentAccount := "test-account"
Expand Down
65 changes: 5 additions & 60 deletions exchange/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/prebid/prebid-server/privacy"
"github.com/prebid/prebid-server/privacy/ccpa"
"github.com/prebid/prebid-server/privacy/lmt"
"github.com/prebid/prebid-server/schain"
)

var integrationTypeMap = map[metrics.RequestType]config.IntegrationType{
Expand All @@ -31,23 +32,6 @@ var integrationTypeMap = map[metrics.RequestType]config.IntegrationType{

const unknownBidder string = ""

func BidderToPrebidSChains(sChains []*openrtb_ext.ExtRequestPrebidSChain) (map[string]*openrtb_ext.ExtRequestPrebidSChainSChain, error) {
Copy link
Collaborator Author

@bsardo bsardo Dec 13, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function was copied to schain/schain.go.

bidderToSChains := make(map[string]*openrtb_ext.ExtRequestPrebidSChainSChain)

for _, schainWrapper := range sChains {
for _, bidder := range schainWrapper.Bidders {
if _, present := bidderToSChains[bidder]; present {
return nil, fmt.Errorf("request.ext.prebid.schains contains multiple schains for bidder %s; "+
"it must contain no more than one per bidder.", bidder)
} else {
bidderToSChains[bidder] = &schainWrapper.SChain
}
}
}

return bidderToSChains, nil
}

// cleanOpenRTBRequests splits the input request into requests which are sanitized for each bidder. Intended behavior is:
//
// 1. BidRequest.Imp[].Ext will only contain the "prebid" field and a "bidder" field which has the params for the intended Bidder.
Expand Down Expand Up @@ -228,14 +212,9 @@ func getAuctionBidderRequests(req AuctionRequest,
return nil, []error{err}
}

var sChainsByBidder map[string]*openrtb_ext.ExtRequestPrebidSChainSChain

// Quick extra wrapper until RequestWrapper makes its way into CleanRequests
if requestExt != nil {
sChainsByBidder, err = BidderToPrebidSChains(requestExt.Prebid.SChains)
if err != nil {
return nil, []error{err}
}
sChainWriter, err := schain.NewSChainWriter(requestExt)
if err != nil {
return nil, []error{err}
}

var errs []error
Expand All @@ -245,7 +224,7 @@ func getAuctionBidderRequests(req AuctionRequest,
reqCopy := *req.BidRequest
reqCopy.Imp = imps

prepareSource(&reqCopy, bidder, sChainsByBidder)
sChainWriter.Write(&reqCopy, bidder)

if len(bidderParamsInReqExt) != 0 {

Expand Down Expand Up @@ -318,40 +297,6 @@ func getExtJson(req *openrtb2.BidRequest, unpackedExt *openrtb_ext.ExtRequest) (
return json.Marshal(extCopy)
}

func prepareSource(req *openrtb2.BidRequest, bidder string, sChainsByBidder map[string]*openrtb_ext.ExtRequestPrebidSChainSChain) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic has been moved to the schain writer implementation Write methods.

const sChainWildCard = "*"
var selectedSChain *openrtb_ext.ExtRequestPrebidSChainSChain

wildCardSChain := sChainsByBidder[sChainWildCard]
bidderSChain := sChainsByBidder[bidder]

// source should not be modified
if bidderSChain == nil && wildCardSChain == nil {
return
}

if bidderSChain != nil {
selectedSChain = bidderSChain
} else {
selectedSChain = wildCardSChain
}

// set source
if req.Source == nil {
req.Source = &openrtb2.Source{}
} else {
sourceCopy := *req.Source
req.Source = &sourceCopy
}
schain := openrtb_ext.ExtRequestPrebidSChain{
SChain: *selectedSChain,
}
sourceExt, err := json.Marshal(schain)
if err == nil {
req.Source.Ext = sourceExt
}
}

// extractBuyerUIDs parses the values from user.ext.prebid.buyeruids, and then deletes those values from the ext.
// This prevents a Bidder from using these values to figure out who else is involved in the Auction.
func extractBuyerUIDs(user *openrtb2.User) (map[string]string, error) {
Expand Down
Loading