Skip to content

Commit

Permalink
Add ORTB 2.4 schain support (prebid#2108)
Browse files Browse the repository at this point in the history
  • Loading branch information
bsardo authored and shunj-nb committed Nov 8, 2022
1 parent e100f7c commit 6e76b57
Show file tree
Hide file tree
Showing 12 changed files with 764 additions and 258 deletions.
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 @@ -536,6 +537,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 @@ -600,6 +605,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 @@ -629,7 +660,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 @@ -2463,6 +2463,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) {
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) {
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

0 comments on commit 6e76b57

Please sign in to comment.