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 Beintoo adapter #1274

Merged
merged 63 commits into from
May 14, 2020
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
3748413
Add Beintoo adapter
ddantuonobeintoo Apr 24, 2020
7f2e8ec
Create beintoo_test.go
ddantuonobeintoo Apr 24, 2020
34af94d
Create params_test.go
ddantuonobeintoo Apr 24, 2020
2116bc8
Create usersync.go
ddantuonobeintoo Apr 24, 2020
5e0994d
Create usersync_test.go
ddantuonobeintoo Apr 24, 2020
f8d9f26
Create banner.json
ddantuonobeintoo Apr 24, 2020
957ee7a
Delete banner.json
ddantuonobeintoo Apr 24, 2020
6d2b135
Create minimal-banner.json
ddantuonobeintoo Apr 24, 2020
08884dc
Create banner.json
ddantuonobeintoo Apr 24, 2020
19b0e95
Create add-bidfloor.json
ddantuonobeintoo Apr 24, 2020
dd5190d
Create bad-imp-banner-missing-sizes.json
ddantuonobeintoo Apr 24, 2020
eef53fd
Create bad-imp-ext-tagid-value.json
ddantuonobeintoo Apr 24, 2020
d05e82f
Create build-banner-object.json
ddantuonobeintoo Apr 24, 2020
2909e88
Create invalid-request-no-banner.json
ddantuonobeintoo Apr 24, 2020
4f91e27
Create invalid-response-no-bids.json
ddantuonobeintoo Apr 24, 2020
6cee779
Create invalid-response-unmarshall-error.json
ddantuonobeintoo Apr 24, 2020
976f223
Create no-imps-in-request.json
ddantuonobeintoo Apr 24, 2020
cbf29d6
Create server-error-code.json
ddantuonobeintoo Apr 24, 2020
d17bcbb
Create server-no-content.json
ddantuonobeintoo Apr 24, 2020
fe92b1b
Create site-domain-and-url-correctly-parsed.json
ddantuonobeintoo Apr 24, 2020
c5267d7
Create imp_beintoo.go
ddantuonobeintoo Apr 24, 2020
e486fb9
Create beintoo.json
ddantuonobeintoo Apr 24, 2020
8274626
Create beintoo.yaml
ddantuonobeintoo Apr 24, 2020
b59dca9
Update syncer.go
ddantuonobeintoo Apr 24, 2020
91fe738
Update syncer_test.go
ddantuonobeintoo Apr 24, 2020
999db70
Update adapter_map.go
ddantuonobeintoo Apr 27, 2020
85a705e
Update bidders.go
ddantuonobeintoo Apr 27, 2020
d77fc77
Update config.go
ddantuonobeintoo Apr 27, 2020
404e14c
Update config.go
ddantuonobeintoo Apr 27, 2020
c317042
Update imp_beintoo.go
ddantuonobeintoo Apr 27, 2020
97b6151
Update config.go
ddantuonobeintoo Apr 27, 2020
59c663b
Update bidders.go
ddantuonobeintoo May 1, 2020
b2802bd
Update usersync_test.go
ddantuonobeintoo May 2, 2020
61042ff
Update beintoo.go
ddantuonobeintoo May 2, 2020
13569fb
Update beintoo.go
ddantuonobeintoo May 2, 2020
3ffa56d
Update beintoo.go
ddantuonobeintoo May 5, 2020
995759e
Update beintoo.go
ddantuonobeintoo May 5, 2020
1bcc9fc
Update beintoo.go
ddantuonobeintoo May 8, 2020
a34d4a6
Update beintoo.go
ddantuonobeintoo May 8, 2020
124379a
Update beintoo.go
ddantuonobeintoo May 8, 2020
8bf3560
Update beintoo.go
ddantuonobeintoo May 8, 2020
1b31e82
Update beintoo.go
ddantuonobeintoo May 8, 2020
8a8400c
Update beintoo.go
ddantuonobeintoo May 8, 2020
1305fd6
Update usersync.go
ddantuonobeintoo May 11, 2020
1955e75
Update usersync_test.go
ddantuonobeintoo May 11, 2020
d3a089c
Update usersync_test.go
ddantuonobeintoo May 11, 2020
b4aa57b
Update config.go
ddantuonobeintoo May 11, 2020
d7cd578
Update beintoo.go
ddantuonobeintoo May 11, 2020
5bbb409
Update add-bidfloor.json
ddantuonobeintoo May 11, 2020
f3c4d23
Update build-banner-object.json
ddantuonobeintoo May 11, 2020
556d22a
Update invalid-response-no-bids.json
ddantuonobeintoo May 11, 2020
017b5e7
Update invalid-response-unmarshall-error.json
ddantuonobeintoo May 11, 2020
8bd55b2
Update server-error-code.json
ddantuonobeintoo May 11, 2020
40ad4dd
Update server-no-content.json
ddantuonobeintoo May 11, 2020
bf67959
Update site-domain-and-url-correctly-parsed.json
ddantuonobeintoo May 11, 2020
92b0b7a
Update minimal-banner.json
ddantuonobeintoo May 11, 2020
006d3d6
Update beintoo_test.go
ddantuonobeintoo May 11, 2020
9bd5b09
Update beintoo.go
ddantuonobeintoo May 11, 2020
4f20cca
Update build-banner-object.json
ddantuonobeintoo May 12, 2020
0b04282
Update add-bidfloor.json
ddantuonobeintoo May 12, 2020
0b2befa
Update beintoo.go
ddantuonobeintoo May 12, 2020
fb36e23
Update beintoo.go
ddantuonobeintoo May 12, 2020
80006b2
Update beintoo.go
ddantuonobeintoo May 12, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
246 changes: 246 additions & 0 deletions adapters/beintoo/beintoo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
package beintoo

import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"time"

"github.com/mxmCherry/openrtb"
"github.com/prebid/prebid-server/adapters"
"github.com/prebid/prebid-server/errortypes"
"github.com/prebid/prebid-server/openrtb_ext"
)

type BeintooAdapter struct {
endpoint string
testing bool
}

func buildEndpoint(endpoint string, testing bool, timeout int64) string {
if timeout == 0 {
timeout = 1000
}
if testing {
// for passing validation tests
return endpoint + "?t=1000&ts=2060541160"
}
return endpoint + "?t=" + strconv.FormatInt(timeout, 10) + "&ts=" + strconv.FormatInt(time.Now().Unix(), 10) + "&src=pbserver"
}
guscarreon marked this conversation as resolved.
Show resolved Hide resolved

func (a *BeintooAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
var errs []error

if len(request.Imp) == 0 {
return nil, []error{&errortypes.BadInput{
Message: fmt.Sprintf("No Imps in Bid Request"),
}}
}

if errs := preprocess(request); errs != nil && len(errs) > 0 {
return nil, append(errs, &errortypes.BadInput{
SyntaxNode marked this conversation as resolved.
Show resolved Hide resolved
Message: fmt.Sprintf("Error in preprocess of Imp, err: %s", errs),
})
}

data, err := json.Marshal(request)
if err != nil {
return nil, []error{&errortypes.BadInput{
Message: fmt.Sprintf("Error in packaging request to JSON"),
}}
}

headers := http.Header{}
headers.Add("Content-Type", "application/json;charset=utf-8")
headers.Add("Accept", "application/json")

if request.Device != nil {
addHeaderIfNonEmpty(headers, "User-Agent", request.Device.UA)
addHeaderIfNonEmpty(headers, "X-Forwarded-For", request.Device.IP)
addHeaderIfNonEmpty(headers, "Accept-Language", request.Device.Language)
if request.Device.DNT != nil {
addHeaderIfNonEmpty(headers, "DNT", strconv.Itoa(int(*request.Device.DNT)))
}
}
if request.Site != nil {
addHeaderIfNonEmpty(headers, "Referer", request.Site.Page)
}

url := buildEndpoint(a.endpoint, a.testing, request.TMax)

return []*adapters.RequestData{{
Method: "POST",
Uri: url,
Body: data,
Headers: headers,
}}, errs
}

func unpackImpExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpBeintoo, error) {
var bidderExt adapters.ExtImpBidder
if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
return nil, &errortypes.BadInput{
Message: err.Error(),
}
}

var BeintooExt openrtb_ext.ExtImpBeintoo
if err := json.Unmarshal(bidderExt.Bidder, &BeintooExt); err != nil {
return nil, &errortypes.BadInput{
Message: fmt.Sprintf("ignoring imp id=%s, invalid ImpExt", imp.ID),
}
}

tagIDValidation, err := strconv.ParseInt(BeintooExt.TagID, 10, 64)
if err != nil || tagIDValidation == 0 {
return nil, &errortypes.BadInput{
Message: fmt.Sprintf("ignoring imp id=%s, invalid tagid must be a String of numbers", imp.ID),
}
}

if BeintooExt.TagID == "" {
Copy link
Contributor

Choose a reason for hiding this comment

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

Since "tagid" was declared required inside file static/bidder-params/beintoo.json there's no need for this check because the true branch of this if statement will become unreachable in production.

return nil, &errortypes.BadInput{
Message: fmt.Sprintf("Ignoring imp id=%s, no tagid present", imp.ID),
}
}

return &BeintooExt, nil
Copy link
Contributor

Choose a reason for hiding this comment

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

gofmt is still complaining about lines 117 and 118, please remove them and run gofmt adapters/beintoo/beintoo.go before commiting in order to make sure the tests pass.
image

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried to do the test and it doesn't show any other errors

}

func buildImpBanner(imp *openrtb.Imp) error {
imp.Ext = nil

if imp.Banner == nil {
return &errortypes.BadInput{
Message: fmt.Sprintf("Request needs to include a Banner object"),
}
}

bannerCopy := *imp.Banner
banner := &bannerCopy
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we need the bannerCopy because the buildImpBanner already was passed an imp that is a copy, a copy created by the for statement in line 181 which is a copy valid in its scope. It doesn't hurt to keep it there, but it'd be a small optimization.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm sorry @ddantuonobeintoo you were right. This copy is needed. I'm sorry, can we rollback to use it again?


if banner.W == nil && banner.H == nil {
if len(banner.Format) == 0 {
Copy link
Contributor

Choose a reason for hiding this comment

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

Now that the banner copy was removed, the following errors appear because banner should be referenced as imp.Banner again.

# github.com/prebid/prebid-server/adapters/beintoo [github.com/prebid/prebid-server/adapters/beintoo.test]
./beintoo.go:135:5: undefined: banner
./beintoo.go:136:10: undefined: banner
./beintoo.go:141:13: undefined: banner
./beintoo.go:142:3: undefined: banner
./beintoo.go:143:3: undefined: banner
./beintoo.go:144:3: undefined: banner
./beintoo.go:145:16: undefined: banner

Copy link
Collaborator

Choose a reason for hiding this comment

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

Also not that the Imp object is local to your adapter, but Imp.Banner is a pointer to the shared Banner object. So you will need to make a copy of Banner and assign that to Imp.Banner if you want to make any changes to Banner.

return &errortypes.BadInput{
Message: fmt.Sprintf("Need at least one size to build request"),
}
}
format := banner.Format[0]
banner.Format = banner.Format[1:]
banner.W = &format.W
banner.H = &format.H
imp.Banner = banner
}

return nil
}

// Add Beintoo required properties to Imp object
func addImpProps(imp *openrtb.Imp, secure *int8, BeintooExt *openrtb_ext.ExtImpBeintoo) {
imp.TagID = BeintooExt.TagID
imp.Secure = secure

if BeintooExt.BidFloor != "" {
bidFloor, err := strconv.ParseFloat(BeintooExt.BidFloor, 64)
if err != nil {
bidFloor = 0
}

if bidFloor > 0 {
imp.BidFloor = bidFloor
imp.BidFloorCur = "USD"
Copy link
Contributor

Choose a reason for hiding this comment

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

Prebid Server supports multiple currencies. Why is this hardcoded to USD?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we normally use USD

}
}

return
}

// Adding header fields to request header
func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue string) {
if len(headerValue) > 0 {
headers.Add(headerName, headerValue)
}
}

// Handle request errors and formatting to be sent to Beintoo
func preprocess(request *openrtb.BidRequest) []error {
impsCount := len(request.Imp)
errors := make([]error, 0, impsCount)
resImps := make([]openrtb.Imp, 0, impsCount)
Copy link
Contributor

Choose a reason for hiding this comment

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

Small optimization. No need to create impsCount since len(request.Imp) only reads a lenght from the slice object in O(1) time

168   func preprocess(request *openrtb.BidRequest) []error {
169 -     impsCount := len(request.Imp)
170 -     errors := make([]error, 0, impsCount)
171 -     resImps := make([]openrtb.Imp, 0, impsCount)
    +     errors := make([]error, 0, len(request.Imp))
    +     resImps := make([]openrtb.Imp, 0, len(request.Imp))
172       secure := int8(0)
adapters/beintoo/beintoo.go

secure := int8(0)

if request.Site != nil && request.Site.Page != "" {
pageURL, err := url.Parse(request.Site.Page)
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably we want to call url.Parse only once and use that url object for both this and the buildEndpoint(endpoint string, testing bool, timeout int64) function.

if err == nil && pageURL.Scheme == "https" {
secure = int8(1)
}
}

for _, imp := range request.Imp {
BeintooExt, err := unpackImpExt(&imp)
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick: Go naming conventions are to start local variables with a lowercase letter.

if err != nil {
errors = append(errors, err)
continue
}

addImpProps(&imp, &secure, BeintooExt)

if err := buildImpBanner(&imp); err != nil {
errors = append(errors, err)
continue
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Function preprocess calls continue everytime it finds an error looking for the good entries in the request.Imp array. But the if statement in line 57 is zero tolerant for any errors making the continue statements unnecessary:

 48 func (a *BeintooAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
 49     var errs []error
 50
 51     if len(request.Imp) == 0 {
 52         return nil, []error{&errortypes.BadInput{
 53             Message: fmt.Sprintf("No Imps in Bid Request"),
 54         }}
 55     }
 56
 57     if errs := preprocess(request); errs != nil && len(errs) > 0 { //<-- exits even if only one error was found
 58         return nil, append(errs, &errortypes.BadInput{
 59             Message: fmt.Sprintf("Error in preprocess of Imp, err: %s", errs),
 60         })
 61     }
 62
adapters/beintoo/beintoo.go

Under this logic, we might as well:

177 - func preprocess(request *openrtb.BidRequest) []error {
    + func preprocess(request *openrtb.BidRequest) error {
178 -     errors := make([]error, 0, len(request.Imp))
179       resImps := make([]openrtb.Imp, 0, len(request.Imp))
180       secure := int8(0)
181  
182   *--  6 lines: if request.Site != nil && request.Site.Page != "" {-----
188  
189       for _, imp := range request.Imp {
190           BeintooExt, err := unpackImpExt(&imp)
191           if err != nil {
192 -             errors = append(errors, err)
193 -             continue
    +             return err
194           }
195  
196           addImpProps(&imp, &secure, BeintooExt)
197  
198           if err := buildImpBanner(&imp); err != nil {
199 -             errors = append(errors, err)
200 -             continue
    +             return err
201           }
202           resImps = append(resImps, imp)
203       }
204  
205       request.Imp = resImps
206  
207 -     return errors
    +     return nil
208   }
adapters/beintoo/beintoo.go

Is this what we intended?

resImps = append(resImps, imp)
}

request.Imp = resImps

return errors
}

// MakeBids make the bids for the bid response.
func (a *BeintooAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {

if response.StatusCode == http.StatusNoContent {
// no bid response
return nil, nil
}

if response.StatusCode != http.StatusOK {
return nil, []error{&errortypes.BadServerResponse{
Message: fmt.Sprintf("Invalid Status Returned: %d. Run with request.debug = 1 for more info", response.StatusCode),
}}
}

var bidResp openrtb.BidResponse

if err := json.Unmarshal(response.Body, &bidResp); err != nil {
return nil, []error{&errortypes.BadServerResponse{
Message: fmt.Sprintf("Unable to unpackage bid response. Error: %s", err.Error()),
}}
}

bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)

for _, sb := range bidResp.SeatBid {
for i := range sb.Bid {
sb.Bid[i].ImpID = sb.Bid[i].ID

bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
Bid: &sb.Bid[i],
BidType: "banner",
})
}
}

return bidResponse, nil

}

func NewBeintooBidder(endpoint string) *BeintooAdapter {
return &BeintooAdapter{
endpoint: endpoint,
testing: false,
}
}
13 changes: 13 additions & 0 deletions adapters/beintoo/beintoo_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package beintoo

import (
"testing"

"github.com/prebid/prebid-server/adapters/adapterstest"
)

func TestJsonSamples(t *testing.T) {
beintooAdapter := NewBeintooBidder("https://ib.beintoo.com")
beintooAdapter.testing = true
adapterstest.RunJSONBidderTest(t, "beintootest", beintooAdapter)
}
117 changes: 117 additions & 0 deletions adapters/beintoo/beintootest/exemplary/minimal-banner.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
{
"mockBidRequest": {
"id": "some_test_auction",
"imp": [{
"id": "some_test_ad_id",
"banner": {
"format": [{
"w": 300,
"h": 250
}],
"w": 300,
"h": 250
},
"ext": {
"bidder": {
"tagid": "25251"
}
}
}],
"device": {
"ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36",
"ip": "123.123.123.123",
"dnt": 1
},
"site": {
"domain": "www.publisher.com",
"page": "http://www.publisher.com/awesome/site?with=some&parameters=here"
}
},

"httpCalls": [{
"expectedRequest": {
"uri": "https://ib.beintoo.com?t=1000&ts=2060541160",
"headers": {
"Accept": [
"application/json"
],
"Content-Type": [
"application/json;charset=utf-8"
],
"X-Forwarded-For": [
"123.123.123.123"
],
"Referer": [
"http://www.publisher.com/awesome/site?with=some&parameters=here"
],
"Dnt": [
"1"
],
"User-Agent": [
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36"
]
},
"body": {
"id": "some_test_auction",
"imp": [{
"id": "some_test_ad_id",
"banner": {
"format": [{
"w": 300,
"h": 250
}],
"w": 300,
"h": 250
},
"tagid": "25251",
"secure": 0
}],
"site": {
"domain": "www.publisher.com",
"page": "http://www.publisher.com/awesome/site?with=some&parameters=here"
},
"device": {
"ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36",
"ip": "123.123.123.123",
"dnt": 1
}
}
},
"mockResponse": {
"status": 200,
"body": {
"id": "some_test_auction",
"seatbid": [{
"seat": "12356",
"bid": [{
"adm": "<div id=\"123456789_ad\"><script>!function(){console.log\"Hello, world.\";}();<\/script><\/div>",
"id": "uuid",
"impid": "some_test_ad_id",
"ttl": 300,
"crid": "94395500",
"w": 300,
"price": 2.942808,
"adid": "94395500",
"h": 250
}]
}],
"cur": "USD"
}
}
}],

"expectedBids": [{
"bid": {
"adm": "<div id=\"123456789_ad\"><script>!function(){console.log\"Hello, world.\";}();<\/script><\/div>",
"id": "uuid",
"impid": "some_test_ad_id",
"ttl": 300,
"crid": "94395500",
"w": 300,
"price": 2.942808,
"adid": "94395500",
"h": 250
},
"type": "banner"
}]
}
4 changes: 4 additions & 0 deletions adapters/beintoo/beintootest/params/race/banner.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"tagid": "25251",
"bidfloor": "0.01"
}
Loading