Skip to content

Commit

Permalink
Adapter Name Case Insensitive: First Party Data (#3211)
Browse files Browse the repository at this point in the history
  • Loading branch information
SyntaxNode authored Oct 18, 2023
1 parent 17109ca commit 11ef54d
Show file tree
Hide file tree
Showing 36 changed files with 1,593 additions and 698 deletions.
1 change: 1 addition & 0 deletions exchange/exchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ type BidderRequest struct {
BidderCoreName openrtb_ext.BidderName
BidderLabels metrics.AdapterLabels
BidderStoredResponses map[string]json.RawMessage
IsRequestAlias bool
ImpReplaceImpId map[string]bool
}

Expand Down
54 changes: 36 additions & 18 deletions exchange/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,7 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context,

}

if auctionReq.FirstPartyData != nil && auctionReq.FirstPartyData[bidderRequest.BidderName] != nil {
applyFPD(auctionReq.FirstPartyData[bidderRequest.BidderName], bidderRequest.BidRequest)
}
applyFPD(auctionReq.FirstPartyData, bidderRequest)

privacyEnforcement.TID = !auctionReq.Activities.Allow(privacy.ActivityTransmitTIDs, scopedName, privacy.NewRequestFromBidRequest(*req))

Expand Down Expand Up @@ -335,7 +333,7 @@ func getAuctionBidderRequests(auctionRequest AuctionRequest,

var errs []error
for bidder, imps := range impsByBidder {
coreBidder := resolveBidder(bidder, aliases)
coreBidder, isRequestAlias := resolveBidder(bidder, aliases)

reqCopy := *req.BidRequest
reqCopy.Imp = imps
Expand All @@ -355,6 +353,7 @@ func getAuctionBidderRequests(auctionRequest AuctionRequest,
bidderRequest := BidderRequest{
BidderName: openrtb_ext.BidderName(bidder),
BidderCoreName: coreBidder,
IsRequestAlias: isRequestAlias,
BidRequest: &reqCopy,
BidderLabels: metrics.AdapterLabels{
Source: auctionRequest.LegacyLabels.Source,
Expand Down Expand Up @@ -768,12 +767,14 @@ func setUserExtWithCopy(request *openrtb2.BidRequest, userExtJSON json.RawMessag
}

// resolveBidder returns the known BidderName associated with bidder, if bidder is an alias. If it's not an alias, the bidder is returned.
func resolveBidder(bidder string, aliases map[string]string) openrtb_ext.BidderName {
func resolveBidder(bidder string, requestAliases map[string]string) (openrtb_ext.BidderName, bool) {
normalisedBidderName, _ := openrtb_ext.NormalizeBidderName(bidder)
if coreBidder, ok := aliases[bidder]; ok {
return openrtb_ext.BidderName(coreBidder)

if coreBidder, ok := requestAliases[bidder]; ok {
return openrtb_ext.BidderName(coreBidder), true
}
return normalisedBidderName

return normalisedBidderName, false
}

// parseAliases parses the aliases from the BidRequest
Expand Down Expand Up @@ -909,20 +910,36 @@ func getExtBidAdjustmentFactors(requestExtPrebid *openrtb_ext.ExtRequestPrebid)
return nil
}

func applyFPD(fpd *firstpartydata.ResolvedFirstPartyData, bidReq *openrtb2.BidRequest) {
if fpd.Site != nil {
bidReq.Site = fpd.Site
func applyFPD(fpd map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData, r BidderRequest) {
if fpd == nil {
return
}
if fpd.App != nil {
bidReq.App = fpd.App

bidder := r.BidderCoreName
if r.IsRequestAlias {
bidder = r.BidderName
}
if fpd.User != nil {

fpdToApply, exists := fpd[bidder]
if !exists || fpdToApply == nil {
return
}

if fpdToApply.Site != nil {
r.BidRequest.Site = fpdToApply.Site
}

if fpdToApply.App != nil {
r.BidRequest.App = fpdToApply.App
}

if fpdToApply.User != nil {
//BuyerUID is a value obtained between fpd extraction and fpd application.
//BuyerUID needs to be set back to fpd before applying this fpd to final bidder request
if bidReq.User != nil && len(bidReq.User.BuyerUID) > 0 {
fpd.User.BuyerUID = bidReq.User.BuyerUID
if r.BidRequest.User != nil && len(r.BidRequest.User.BuyerUID) > 0 {
fpdToApply.User.BuyerUID = r.BidRequest.User.BuyerUID
}
bidReq.User = fpd.User
r.BidRequest.User = fpdToApply.User
}
}

Expand All @@ -934,13 +951,14 @@ func buildBidResponseRequest(req *openrtb2.BidRequest,
bidderToBidderResponse := make(map[openrtb_ext.BidderName]BidderRequest)

for bidderName, impResps := range bidderImpResponses {
resolvedBidder := resolveBidder(string(bidderName), aliases)
resolvedBidder, isRequestAlias := resolveBidder(string(bidderName), aliases)
bidderToBidderResponse[bidderName] = BidderRequest{
BidRequest: req,
BidderCoreName: resolvedBidder,
BidderName: bidderName,
BidderStoredResponses: impResps,
ImpReplaceImpId: bidderImpReplaceImpID[string(resolvedBidder)],
IsRequestAlias: isRequestAlias,
BidderLabels: metrics.AdapterLabels{Adapter: resolvedBidder},
}
}
Expand Down
151 changes: 118 additions & 33 deletions exchange/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3242,60 +3242,145 @@ func TestCleanOpenRTBRequestsBidAdjustment(t *testing.T) {
}

func TestApplyFPD(t *testing.T) {

testCases := []struct {
description string
inputFpd firstpartydata.ResolvedFirstPartyData
inputRequest openrtb2.BidRequest
expectedRequest openrtb2.BidRequest
description string
inputFpd map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData
inputBidderName string
inputBidderCoreName string
inputBidderIsRequestAlias bool
inputRequest openrtb2.BidRequest
expectedRequest openrtb2.BidRequest
}{
{
description: "req.Site defined; bidderFPD.Site not defined; expect request.Site remains the same",
inputFpd: firstpartydata.ResolvedFirstPartyData{Site: nil, App: nil, User: nil},
inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}},
expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}},
description: "fpd-nil",
inputFpd: nil,
inputBidderName: "bidderFromRequest",
inputBidderCoreName: "bidderNormalized",
inputBidderIsRequestAlias: false,
inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}},
expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}},
},
{
description: "fpd-bidderdata-nil",
inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{
"bidderNormalized": nil,
},
inputBidderName: "bidderFromRequest",
inputBidderCoreName: "bidderNormalized",
inputBidderIsRequestAlias: false,
inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}},
expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}},
},
{
description: "fpd-bidderdata-notdefined",
inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{
"differentBidder": {App: &openrtb2.App{ID: "AppId"}},
},
inputBidderName: "bidderFromRequest",
inputBidderCoreName: "bidderNormalized",
inputBidderIsRequestAlias: false,
inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}},
expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}},
},
{
description: "fpd-bidderdata-alias",
inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{
"alias": {App: &openrtb2.App{ID: "AppId"}},
},
inputBidderName: "alias",
inputBidderCoreName: "bidder",
inputBidderIsRequestAlias: true,
inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}},
expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}},
},
{
description: "req.Site defined; bidderFPD.Site not defined; expect request.Site remains the same",
inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{
"bidderNormalized": {Site: nil, App: nil, User: nil},
},
inputBidderName: "bidderFromRequest",
inputBidderCoreName: "bidderNormalized",
inputBidderIsRequestAlias: false,
inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}},
expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}},
},
{
description: "req.Site, req.App, req.User are not defined; bidderFPD.App, bidderFPD.Site and bidderFPD.User defined; " +
"expect req.Site, req.App, req.User to be overriden by bidderFPD.App, bidderFPD.Site and bidderFPD.User",
inputFpd: firstpartydata.ResolvedFirstPartyData{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}},
inputRequest: openrtb2.BidRequest{},
expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}},
inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{
"bidderNormalized": {Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}},
},
inputBidderName: "bidderFromRequest",
inputBidderCoreName: "bidderNormalized",
inputBidderIsRequestAlias: false,
inputRequest: openrtb2.BidRequest{},
expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}},
},
{
description: "req.Site, defined; bidderFPD.App defined; expect request.App to be overriden by bidderFPD.App; expect req.Site remains the same",
inputFpd: firstpartydata.ResolvedFirstPartyData{App: &openrtb2.App{ID: "AppId"}},
inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}},
expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}},
description: "req.Site, defined; bidderFPD.App defined; expect request.App to be overriden by bidderFPD.App; expect req.Site remains the same",
inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{
"bidderNormalized": {App: &openrtb2.App{ID: "AppId"}},
},
inputBidderName: "bidderFromRequest",
inputBidderCoreName: "bidderNormalized",
inputBidderIsRequestAlias: false,
inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}},
expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}},
},
{
description: "req.Site, req.App defined; bidderFPD.App defined; expect request.App to be overriden by bidderFPD.App",
inputFpd: firstpartydata.ResolvedFirstPartyData{App: &openrtb2.App{ID: "AppId"}},
inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "TestAppId"}},
expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}},
description: "req.Site, req.App defined; bidderFPD.App defined; expect request.App to be overriden by bidderFPD.App",
inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{
"bidderNormalized": {App: &openrtb2.App{ID: "AppId"}},
},
inputBidderName: "bidderFromRequest",
inputBidderCoreName: "bidderNormalized",
inputBidderIsRequestAlias: false,
inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "TestAppId"}},
expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}},
},
{
description: "req.User is defined; bidderFPD.User defined; req.User has BuyerUID. Expect to see user.BuyerUID in result request",
inputFpd: firstpartydata.ResolvedFirstPartyData{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}},
inputRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserIdIn", BuyerUID: "12345"}},
expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserId", BuyerUID: "12345"}, Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}},
description: "req.User is defined; bidderFPD.User defined; req.User has BuyerUID. Expect to see user.BuyerUID in result request",
inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{
"bidderNormalized": {Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}},
},
inputBidderName: "bidderFromRequest",
inputBidderCoreName: "bidderNormalized",
inputBidderIsRequestAlias: false,
inputRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserIdIn", BuyerUID: "12345"}},
expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserId", BuyerUID: "12345"}, Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}},
},
{
description: "req.User is defined; bidderFPD.User defined; req.User has BuyerUID with zero length. Expect to see empty user.BuyerUID in result request",
inputFpd: firstpartydata.ResolvedFirstPartyData{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}},
inputRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserIdIn", BuyerUID: ""}},
expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserId"}, Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}},
description: "req.User is defined; bidderFPD.User defined; req.User has BuyerUID with zero length. Expect to see empty user.BuyerUID in result request",
inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{
"bidderNormalized": {Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}},
},
inputBidderName: "bidderFromRequest",
inputBidderCoreName: "bidderNormalized",
inputBidderIsRequestAlias: false,
inputRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserIdIn", BuyerUID: ""}},
expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserId"}, Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}},
},
{
description: "req.User is not defined; bidderFPD.User defined and has BuyerUID. Expect to see user.BuyerUID in result request",
inputFpd: firstpartydata.ResolvedFirstPartyData{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", BuyerUID: "FPDBuyerUID"}},
inputRequest: openrtb2.BidRequest{},
expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", BuyerUID: "FPDBuyerUID"}},
description: "req.User is not defined; bidderFPD.User defined and has BuyerUID. Expect to see user.BuyerUID in result request",
inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{
"bidderNormalized": {Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", BuyerUID: "FPDBuyerUID"}},
},
inputBidderName: "bidderFromRequest",
inputBidderCoreName: "bidderNormalized",
inputBidderIsRequestAlias: false,
inputRequest: openrtb2.BidRequest{},
expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", BuyerUID: "FPDBuyerUID"}},
},
}

for _, testCase := range testCases {
applyFPD(&testCase.inputFpd, &testCase.inputRequest)
bidderRequest := BidderRequest{
BidderName: openrtb_ext.BidderName(testCase.inputBidderName),
BidderCoreName: openrtb_ext.BidderName(testCase.inputBidderCoreName),
IsRequestAlias: testCase.inputBidderIsRequestAlias,
BidRequest: &testCase.inputRequest,
}
applyFPD(testCase.inputFpd, bidderRequest)
assert.Equal(t, testCase.expectedRequest, testCase.inputRequest, fmt.Sprintf("incorrect request after applying fpd, testcase %s", testCase.description))
}
}
Expand Down
24 changes: 11 additions & 13 deletions firstpartydata/first_party_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,9 @@ func ResolveFPD(bidRequest *openrtb2.BidRequest, fpdBidderConfigData map[openrtb
}
} else {
// only bidders in global bidder list will receive global data and bidder specific data
for _, bidderName := range biddersWithGlobalFPD {
for _, bidder := range biddersWithGlobalFPD {
bidderName := openrtb_ext.NormalizeBidderNameOrUnchanged(bidder)

if _, present := allBiddersTable[string(bidderName)]; !present {
allBiddersTable[string(bidderName)] = struct{}{}
}
Expand Down Expand Up @@ -462,12 +464,14 @@ func buildExtData(data []byte) []byte {
// ExtractBidderConfigFPD extracts bidder specific configs from req.ext.prebid.bidderconfig
func ExtractBidderConfigFPD(reqExt *openrtb_ext.RequestExt) (map[openrtb_ext.BidderName]*openrtb_ext.ORTB2, error) {
fpd := make(map[openrtb_ext.BidderName]*openrtb_ext.ORTB2)

reqExtPrebid := reqExt.GetPrebid()
if reqExtPrebid != nil {
for _, bidderConfig := range reqExtPrebid.BidderConfigs {
for _, bidder := range bidderConfig.Bidders {
if _, present := fpd[openrtb_ext.BidderName(bidder)]; present {
//if bidder has duplicated config - throw an error
bidderName := openrtb_ext.NormalizeBidderNameOrUnchanged(bidder)

if _, duplicate := fpd[bidderName]; duplicate {
return nil, &errortypes.BadInput{
Message: fmt.Sprintf("multiple First Party Data bidder configs provided for bidder: %s", bidder),
}
Expand All @@ -476,18 +480,12 @@ func ExtractBidderConfigFPD(reqExt *openrtb_ext.RequestExt) (map[openrtb_ext.Bid
fpdBidderData := &openrtb_ext.ORTB2{}

if bidderConfig.Config != nil && bidderConfig.Config.ORTB2 != nil {
if bidderConfig.Config.ORTB2.Site != nil {
fpdBidderData.Site = bidderConfig.Config.ORTB2.Site
}
if bidderConfig.Config.ORTB2.App != nil {
fpdBidderData.App = bidderConfig.Config.ORTB2.App
}
if bidderConfig.Config.ORTB2.User != nil {
fpdBidderData.User = bidderConfig.Config.ORTB2.User
}
fpdBidderData.Site = bidderConfig.Config.ORTB2.Site
fpdBidderData.App = bidderConfig.Config.ORTB2.App
fpdBidderData.User = bidderConfig.Config.ORTB2.User
}

fpd[openrtb_ext.BidderName(bidder)] = fpdBidderData
fpd[bidderName] = fpdBidderData
}
}
reqExtPrebid.BidderConfigs = nil
Expand Down
Loading

0 comments on commit 11ef54d

Please sign in to comment.