-
Notifications
You must be signed in to change notification settings - Fork 720
/
bidder_validate_bids.go
129 lines (116 loc) · 4.64 KB
/
bidder_validate_bids.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package exchange
import (
"context"
"errors"
"fmt"
"strings"
"github.com/prebid/openrtb/v19/openrtb2"
"github.com/prebid/prebid-server/v2/adapters"
"github.com/prebid/prebid-server/v2/currency"
"github.com/prebid/prebid-server/v2/exchange/entities"
"github.com/prebid/prebid-server/v2/experiment/adscert"
"github.com/prebid/prebid-server/v2/hooks/hookexecution"
"github.com/prebid/prebid-server/v2/openrtb_ext"
goCurrency "golang.org/x/text/currency"
)
// addValidatedBidderMiddleware returns a bidder that removes invalid bids from the argument bidder's response.
// These will be converted into errors instead.
//
// The goal here is to make sure that the response contains Bids which are valid given the initial Request,
// so that Publishers can trust the Bids they get from Prebid Server.
func addValidatedBidderMiddleware(bidder AdaptedBidder) AdaptedBidder {
return &validatedBidder{
bidder: bidder,
}
}
type validatedBidder struct {
bidder AdaptedBidder
}
func (v *validatedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) ([]*entities.PbsOrtbSeatBid, extraBidderRespInfo, []error) {
seatBids, extraBidderRespInfo, errs := v.bidder.requestBid(ctx, bidderRequest, conversions, reqInfo, adsCertSigner, bidRequestOptions, alternateBidderCodes, hookExecutor, ruleToAdjustments)
for _, seatBid := range seatBids {
if validationErrors := removeInvalidBids(bidderRequest.BidRequest, seatBid); len(validationErrors) > 0 {
errs = append(errs, validationErrors...)
}
}
return seatBids, extraBidderRespInfo, errs
}
// validateBids will run some validation checks on the returned bids and excise any invalid bids
func removeInvalidBids(request *openrtb2.BidRequest, seatBid *entities.PbsOrtbSeatBid) []error {
// Exit early if there is nothing to do.
if seatBid == nil || len(seatBid.Bids) == 0 {
return nil
}
// By design, default currency is USD.
if cerr := validateCurrency(request.Cur, seatBid.Currency); cerr != nil {
seatBid.Bids = nil
return []error{cerr}
}
errs := make([]error, 0, len(seatBid.Bids))
validBids := make([]*entities.PbsOrtbBid, 0, len(seatBid.Bids))
for _, bid := range seatBid.Bids {
if ok, berr := validateBid(bid); ok {
validBids = append(validBids, bid)
} else {
errs = append(errs, berr)
}
}
seatBid.Bids = validBids
return errs
}
// validateCurrency will run currency validation checks and return true if it passes, false otherwise.
func validateCurrency(requestAllowedCurrencies []string, bidCurrency string) error {
// Default currency is `USD` by design.
defaultCurrency := "USD"
// Make sure bid currency is a valid ISO currency code
if bidCurrency == "" {
// If bid currency is not set, then consider it's default currency.
bidCurrency = defaultCurrency
}
currencyUnit, cerr := goCurrency.ParseISO(bidCurrency)
if cerr != nil {
return cerr
}
// Make sure the bid currency is allowed from bid request via `cur` field.
// If `cur` field array from bid request is empty, then consider it accepts the default currency.
currencyAllowed := false
if len(requestAllowedCurrencies) == 0 {
requestAllowedCurrencies = []string{defaultCurrency}
}
for _, allowedCurrency := range requestAllowedCurrencies {
if strings.ToUpper(allowedCurrency) == currencyUnit.String() {
currencyAllowed = true
break
}
}
if !currencyAllowed {
return fmt.Errorf(
"Bid currency is not allowed. Was '%s', wants: ['%s']",
currencyUnit.String(),
strings.Join(requestAllowedCurrencies, "', '"),
)
}
return nil
}
// validateBid will run the supplied bid through validation checks and return true if it passes, false otherwise.
func validateBid(bid *entities.PbsOrtbBid) (bool, error) {
if bid.Bid == nil {
return false, errors.New("Empty bid object submitted.")
}
if bid.Bid.ID == "" {
return false, errors.New("Bid missing required field 'id'")
}
if bid.Bid.ImpID == "" {
return false, fmt.Errorf("Bid \"%s\" missing required field 'impid'", bid.Bid.ID)
}
if bid.Bid.Price < 0.0 {
return false, fmt.Errorf("Bid \"%s\" does not contain a positive (or zero if there is a deal) 'price'", bid.Bid.ID)
}
if bid.Bid.Price == 0.0 && bid.Bid.DealID == "" {
return false, fmt.Errorf("Bid \"%s\" does not contain positive 'price' which is required since there is no deal set for this bid", bid.Bid.ID)
}
if bid.Bid.CrID == "" {
return false, fmt.Errorf("Bid \"%s\" missing creative ID", bid.Bid.ID)
}
return true, nil
}