Skip to content

Commit

Permalink
Add Support For imp.ext.prebid For DealTiers (#1539)
Browse files Browse the repository at this point in the history
* Add Support For imp.ext.prebid For DealTiers

* Remove Normalization
  • Loading branch information
SyntaxNode authored Oct 28, 2020
1 parent 0742c81 commit 5a718e7
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 131 deletions.
48 changes: 13 additions & 35 deletions exchange/exchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,34 +238,19 @@ func recordImpMetrics(bidRequest *openrtb.BidRequest, metricsEngine pbsmetrics.M
}
}

type DealTierInfo struct {
Prefix string `json:"prefix"`
MinDealTier int `json:"minDealTier"`
}

type DealTier struct {
Info *DealTierInfo `json:"dealTier,omitempty"`
}

type BidderDealTier struct {
DealInfo map[string]*DealTier
}

// applyDealSupport updates targeting keys with deal prefixes if minimum deal tier exceeded
func applyDealSupport(bidRequest *openrtb.BidRequest, auc *auction, bidCategory map[string]string) []error {
errs := []error{}
impDealMap := getDealTiers(bidRequest)

for impID, topBidsPerImp := range auc.winningBidsByBidder {
impDeal := impDealMap[impID].DealInfo
impDeal := impDealMap[impID]
for bidder, topBidPerBidder := range topBidsPerImp {
bidderString := bidder.String()

if topBidPerBidder.dealPriority > 0 {
if validateAndNormalizeDealTier(impDeal[bidderString]) {
updateHbPbCatDur(topBidPerBidder, impDeal[bidderString].Info, bidCategory)
if validateDealTier(impDeal[bidder]) {
updateHbPbCatDur(topBidPerBidder, impDeal[bidder], bidCategory)
} else {
errs = append(errs, fmt.Errorf("dealTier configuration invalid for bidder '%s', imp ID '%s'", bidderString, impID))
errs = append(errs, fmt.Errorf("dealTier configuration invalid for bidder '%s', imp ID '%s'", string(bidder), impID))
}
}
}
Expand All @@ -275,34 +260,27 @@ func applyDealSupport(bidRequest *openrtb.BidRequest, auc *auction, bidCategory
}

// getDealTiers creates map of impression to bidder deal tier configuration
func getDealTiers(bidRequest *openrtb.BidRequest) map[string]*BidderDealTier {
impDealMap := make(map[string]*BidderDealTier)
func getDealTiers(bidRequest *openrtb.BidRequest) map[string]openrtb_ext.DealTierBidderMap {
impDealMap := make(map[string]openrtb_ext.DealTierBidderMap)

for _, imp := range bidRequest.Imp {
var bidderDealTier BidderDealTier
err := json.Unmarshal(imp.Ext, &bidderDealTier.DealInfo)
dealTierBidderMap, err := openrtb_ext.ReadDealTiersFromImp(imp)
if err != nil {
continue
}

impDealMap[imp.ID] = &bidderDealTier
impDealMap[imp.ID] = dealTierBidderMap
}

return impDealMap
}

func validateAndNormalizeDealTier(impDeal *DealTier) bool {
if impDeal == nil || impDeal.Info == nil {
return false
}
// Remove whitespace from prefix before checking if it can be used
impDeal.Info.Prefix = strings.ReplaceAll(impDeal.Info.Prefix, " ", "")
return len(impDeal.Info.Prefix) > 0 && impDeal.Info.MinDealTier > 0
func validateDealTier(dealTier openrtb_ext.DealTier) bool {
return len(dealTier.Prefix) > 0 && dealTier.MinDealTier > 0
}

func updateHbPbCatDur(bid *pbsOrtbBid, dealTierInfo *DealTierInfo, bidCategory map[string]string) {
if bid.dealPriority >= dealTierInfo.MinDealTier {
prefixTier := fmt.Sprintf("%s%d_", dealTierInfo.Prefix, bid.dealPriority)
func updateHbPbCatDur(bid *pbsOrtbBid, dealTier openrtb_ext.DealTier, bidCategory map[string]string) {
if bid.dealPriority >= dealTier.MinDealTier {
prefixTier := fmt.Sprintf("%s%d_", dealTier.Prefix, bid.dealPriority)

if oldCatDur, ok := bidCategory[bid.bid.ID]; ok {
oldCatDurSplit := strings.SplitAfterN(oldCatDur, "_", 2)
Expand Down
130 changes: 51 additions & 79 deletions exchange/exchange_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2278,127 +2278,99 @@ func TestApplyDealSupport(t *testing.T) {

func TestGetDealTiers(t *testing.T) {
testCases := []struct {
impExt json.RawMessage
bidderResult map[string]bool // true indicates bidder had valid config, false indicates invalid
description string
request openrtb.BidRequest
expected map[string]openrtb_ext.DealTierBidderMap
}{
{
impExt: json.RawMessage(`{"validbase": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}`),
bidderResult: map[string]bool{
"validbase": true,
description: "None",
request: openrtb.BidRequest{
Imp: []openrtb.Imp{},
},
expected: map[string]openrtb_ext.DealTierBidderMap{},
},
{
impExt: json.RawMessage(`{"validmultiple1": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}, "validmultiple2": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}`),
bidderResult: map[string]bool{
"validmultiple1": true,
"validmultiple2": true,
description: "One",
request: openrtb.BidRequest{
Imp: []openrtb.Imp{
{ID: "imp1", Ext: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}}}`)},
},
},
expected: map[string]openrtb_ext.DealTierBidderMap{
"imp1": {openrtb_ext.BidderAppnexus: {Prefix: "tier", MinDealTier: 5}},
},
},
{
impExt: json.RawMessage(`{"nodealtier": {"placementId": 10433394}}`),
bidderResult: map[string]bool{
"nodealtier": false,
description: "Many",
request: openrtb.BidRequest{
Imp: []openrtb.Imp{
{ID: "imp1", Ext: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier1"}}}`)},
{ID: "imp2", Ext: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 8, "prefix": "tier2"}}}`)},
},
},
expected: map[string]openrtb_ext.DealTierBidderMap{
"imp1": {openrtb_ext.BidderAppnexus: {Prefix: "tier1", MinDealTier: 5}},
"imp2": {openrtb_ext.BidderAppnexus: {Prefix: "tier2", MinDealTier: 8}},
},
},
{
impExt: json.RawMessage(`{"validbase": {"placementId": 10433394}, "onedealTier2": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}`),
bidderResult: map[string]bool{
"onedealTier2": true,
"validbase": false,
description: "Many - Skips Malformed",
request: openrtb.BidRequest{
Imp: []openrtb.Imp{
{ID: "imp1", Ext: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier1"}}}`)},
{ID: "imp2", Ext: json.RawMessage(`{"appnexus": {"dealTier": "wrong type"}}`)},
},
},
expected: map[string]openrtb_ext.DealTierBidderMap{
"imp1": {openrtb_ext.BidderAppnexus: {Prefix: "tier1", MinDealTier: 5}},
},
},
}

filledDealTier := DealTier{
Info: &DealTierInfo{
Prefix: "tier",
MinDealTier: 5,
},
}
emptyDealTier := DealTier{}

for _, test := range testCases {
bidRequest := &openrtb.BidRequest{
ID: "some-request-id",
Imp: []openrtb.Imp{
{
ID: "imp_id1",
Ext: test.impExt,
},
},
}

impDealMap := getDealTiers(bidRequest)

for bidder, valid := range test.bidderResult {
if valid {
assert.Equal(t, &filledDealTier, impDealMap["imp_id1"].DealInfo[bidder], "DealTier should be filled with config data")
} else {
assert.Equal(t, &emptyDealTier, impDealMap["imp_id1"].DealInfo[bidder], "DealTier should be empty")
}
}
result := getDealTiers(&test.request)
assert.Equal(t, test.expected, result, test.description)
}
}

func TestValidateAndNormalizeDealTier(t *testing.T) {
func TestValidateDealTier(t *testing.T) {
testCases := []struct {
description string
params json.RawMessage
dealTier openrtb_ext.DealTier
expectedResult bool
}{
{
description: "BidderDealTier should be valid",
params: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}`),
description: "Valid",
dealTier: openrtb_ext.DealTier{Prefix: "prefix", MinDealTier: 5},
expectedResult: true,
},
{
description: "BidderDealTier should be invalid due to empty prefix",
params: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": ""}, "placementId": 10433394}}`),
description: "Invalid - Empty",
dealTier: openrtb_ext.DealTier{},
expectedResult: false,
},
{
description: "BidderDealTier should be invalid due to empty dealTier",
params: json.RawMessage(`{"appnexus": {"dealTier": {}, "placementId": 10433394}}`),
description: "Invalid - Empty Prefix",
dealTier: openrtb_ext.DealTier{MinDealTier: 5},
expectedResult: false,
},
{
description: "BidderDealTier should be invalid due to missing minDealTier",
params: json.RawMessage(`{"appnexus": {"dealTier": {"prefix": "tier"}, "placementId": 10433394}}`),
description: "Invalid - Empty Deal Tier",
dealTier: openrtb_ext.DealTier{Prefix: "prefix"},
expectedResult: false,
},
{
description: "BidderDealTier should be invalid due to missing dealTier",
params: json.RawMessage(`{"appnexus": {"placementId": 10433394}}`),
expectedResult: false,
},
{
description: "BidderDealTier should be invalid due to prefix containing all whitespace",
params: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": " "}, "placementId": 10433394}}`),
expectedResult: false,
},
{
description: "BidderDealTier should be valid after removing whitespace",
params: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": " prefixwith sp aces "}, "placementId": 10433394}}`),
expectedResult: true,
},
}

for _, test := range testCases {
var bidderDealTier BidderDealTier
err := json.Unmarshal(test.params, &bidderDealTier.DealInfo)
if err != nil {
assert.Fail(t, "Unable to unmarshal JSON data for testing BidderDealTier")
}

assert.Equal(t, test.expectedResult, validateAndNormalizeDealTier(bidderDealTier.DealInfo["appnexus"]), test.description)
assert.Equal(t, test.expectedResult, validateDealTier(test.dealTier), test.description)
}
}

func TestUpdateHbPbCatDur(t *testing.T) {
testCases := []struct {
description string
targ map[string]string
dealTier *DealTierInfo
dealTier openrtb_ext.DealTier
dealPriority int
expectedHbPbCatDur string
}{
Expand All @@ -2408,7 +2380,7 @@ func TestUpdateHbPbCatDur(t *testing.T) {
"hb_pb": "12.00",
"hb_pb_cat_dur": "12.00_movies_30s",
},
dealTier: &DealTierInfo{
dealTier: openrtb_ext.DealTier{
Prefix: "tier",
MinDealTier: 5,
},
Expand All @@ -2421,7 +2393,7 @@ func TestUpdateHbPbCatDur(t *testing.T) {
"hb_pb": "12.00",
"hb_pb_cat_dur": "12.00_auto_30s",
},
dealTier: &DealTierInfo{
dealTier: openrtb_ext.DealTier{
Prefix: "tier",
MinDealTier: 10,
},
Expand All @@ -2434,7 +2406,7 @@ func TestUpdateHbPbCatDur(t *testing.T) {
"hb_pb": "12.00",
"hb_pb_cat_dur": "12.00_medicine_30s",
},
dealTier: &DealTierInfo{
dealTier: openrtb_ext.DealTier{
Prefix: "tier",
MinDealTier: 1,
},
Expand Down
61 changes: 61 additions & 0 deletions openrtb_ext/deal_tier.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package openrtb_ext

import (
"encoding/json"

"github.com/mxmCherry/openrtb"
)

// DealTier defines the configuration of a deal tier.
type DealTier struct {
// Prefix specifies the beginning of the hb_pb_cat_dur targeting key value. Must be non-empty.
Prefix string `json:"prefix"`

// MinDealTier specifies the minimum deal priority value (inclusive) that must be met for the targeting
// key value to be modified. Must be greater than 0.
MinDealTier int `json:"minDealTier"`
}

// DealTierBidderMap defines a correlation between bidders and deal tiers.
type DealTierBidderMap map[BidderName]DealTier

// ReadDealTiersFromImp returns a map of bidder deal tiers read from the impression of an original request (not split / cleaned).
func ReadDealTiersFromImp(imp openrtb.Imp) (DealTierBidderMap, error) {
dealTiers := make(DealTierBidderMap)

if len(imp.Ext) == 0 {
return dealTiers, nil
}

// imp.ext.{bidder}
var impExt map[string]struct {
DealTier *DealTier `json:"dealTier"`
}
if err := json.Unmarshal(imp.Ext, &impExt); err != nil {
return nil, err
}
for bidder, param := range impExt {
if param.DealTier != nil {
dealTiers[BidderName(bidder)] = *param.DealTier
}
}

// imp.ext.prebid.{bidder}
var impPrebidExt struct {
Prebid struct {
Bidders map[string]struct {
DealTier *DealTier `json:"dealTier"`
} `json:"bidder"`
} `json:"prebid"`
}
if err := json.Unmarshal(imp.Ext, &impPrebidExt); err != nil {
return nil, err
}
for bidder, param := range impPrebidExt.Prebid.Bidders {
if param.DealTier != nil {
dealTiers[BidderName(bidder)] = *param.DealTier
}
}

return dealTiers, nil
}
Loading

0 comments on commit 5a718e7

Please sign in to comment.