Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
172 changes: 172 additions & 0 deletions adapters/beop/beop.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package beop

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

"github.com/prebid/openrtb/v20/openrtb2"
"github.com/prebid/prebid-server/v3/adapters"
"github.com/prebid/prebid-server/v3/config"
"github.com/prebid/prebid-server/v3/errortypes"
"github.com/prebid/prebid-server/v3/openrtb_ext"
"github.com/prebid/prebid-server/v3/util/jsonutil"
)

type adapter struct {
endpoint string
}

func Builder(
bidderName openrtb_ext.BidderName,
config config.Adapter,
server config.Server) (
adapters.Bidder, error,
) {
bidder := &adapter{
endpoint: config.Endpoint,
}
return bidder, nil
}

func (a *adapter) getRequestExtImpBeop(imp *openrtb2.Imp) (*openrtb_ext.ExtImpBeop, error) {
var bidderExt adapters.ExtImpBidder
if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil {
return nil, &errortypes.BadInput{
Message: "ext.bidder not provided",
}
}
var beopExt openrtb_ext.ExtImpBeop
if err := jsonutil.Unmarshal(bidderExt.Bidder, &beopExt); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can add coverage for unmarshalling errors in this code path

return nil, &errortypes.BadInput{
Message: "ext.bidder not provided",
}
}
return &beopExt, nil
}

func (a *adapter) buildEndpointURL(params *openrtb_ext.ExtImpBeop) (string, error) {
url, _ := url.Parse(a.endpoint)
query := url.Query()
if pid := params.BeopPublisherID; len(pid) != 0 {
query.Set("pid", pid)
}
if nid := params.BeopNetworkID; len(nid) != 0 {
query.Set("nid", nid)
}
if nptnid := params.BeopNetworkPartnerID; len(nptnid) != 0 {
query.Set("nptnid", nptnid)
}
url.RawQuery = query.Encode()
return url.String(), nil
}

func (a *adapter) MakeRequests(
request *openrtb2.BidRequest,
requestInfo *adapters.ExtraRequestInfo) (
[]*adapters.RequestData, []error,
) {
var beopExt *openrtb_ext.ExtImpBeop
var err error

beopExt, err = a.getRequestExtImpBeop(&request.Imp[0])
if err != nil {
return nil, []error{err}
}

requestJSON, err := json.Marshal(request)
if err != nil {
return nil, []error{err}
}

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

url, err := a.buildEndpointURL(beopExt)
if err != nil {
return nil, []error{err}
}

requestData := &adapters.RequestData{
Method: "POST",
Uri: url,
Body: requestJSON,
Headers: headers,
ImpIDs: openrtb_ext.GetImpIDs(request.Imp),
}

return []*adapters.RequestData{requestData}, nil
}

func getMediaTypeForBid(bid *openrtb2.Bid) (openrtb_ext.BidType, error) {
mType := bid.MType
var bidType openrtb_ext.BidType
switch mType {
case openrtb2.MarkupBanner:
bidType = openrtb_ext.BidTypeBanner
case openrtb2.MarkupVideo:
bidType = openrtb_ext.BidTypeVideo
default:
return bidType, fmt.Errorf("Failed to parse bid mType for impression \"%s\"", bid.ImpID)
}
return bidType, nil
}

func (a *adapter) MakeBids(
request *openrtb2.BidRequest,
requestData *adapters.RequestData,
responseData *adapters.ResponseData) (
*adapters.BidderResponse, []error,
) {
if responseData.StatusCode == http.StatusNoContent {
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing test case

Copy link
Author

Choose a reason for hiding this comment

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

return nil, nil
}

if responseData.StatusCode == http.StatusServiceUnavailable {
return nil, []error{&errortypes.BadInput{
Message: fmt.Sprintf("Service Unavailable. Status Code: [ %d ] ", responseData.StatusCode),
}}
}

if responseData.StatusCode == http.StatusBadRequest {
err := &errortypes.BadServerResponse{
Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", responseData.StatusCode),
}
return nil, []error{err}
}

var responseBody openrtb2.BidResponse
if err := jsonutil.Unmarshal(responseData.Body, &responseBody); err != nil {
return nil, []error{&errortypes.BadServerResponse{
Message: "Bad Server Response",
}}
}

if len(responseBody.SeatBid) == 0 {
return nil, []error{&errortypes.BadServerResponse{
Message: "Empty SeatBid array",
}}
}

bidResponseFinal := adapters.NewBidderResponseWithBidsCapacity(len(responseBody.SeatBid[0].Bid))
seatBid := responseBody.SeatBid[0]
var errors []error
for i := range seatBid.Bid {
bid := &seatBid.Bid[i]
bidType, err := getMediaTypeForBid(bid)
if err != nil {
errors = append(errors, err)
continue
}
bidResponseFinal.Bids = append(bidResponseFinal.Bids, &adapters.TypedBid{
Bid: bid,
BidType: bidType,
})
}
if len(bidResponseFinal.Bids) == 0 {
return nil, errors
}
return bidResponseFinal, errors
}
21 changes: 21 additions & 0 deletions adapters/beop/beop_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package beop

import (
"testing"

"github.com/prebid/prebid-server/v3/adapters/adapterstest"
"github.com/prebid/prebid-server/v3/config"
"github.com/prebid/prebid-server/v3/openrtb_ext"
)

func TestJsonSamples(t *testing.T) {
bidder, buildErr := Builder(openrtb_ext.BidderBeop, config.Adapter{
Endpoint: "http://hb-test.collectiveaudience.co/rtb/bid"},
config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})

if buildErr != nil {
t.Fatalf("Builder returned unexpected error %v", buildErr)
}

adapterstest.RunJSONBidderTest(t, "beoptest", bidder)
}
155 changes: 155 additions & 0 deletions adapters/beop/beoptest/exemplary/banner-web-with-nid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
{
"mockBidRequest": {
"id": "request-id",
"site": {
"page": "test.com/page",
"domain": "test.com",
"cat": [
"IAB9-1"
],
"publisher": {
"id": "123456789"
}
},
"device": {
"ua": "useragent",
"ip": "100.100.100.100",
"language": "en"
},
"tmax": 1000,
"user": {
"id": "some-user"
},
"imp": [
{
"id": "impression-id",
"tagid": "tid",
"banner": {
"w": 320,
"h": 50
},
"ext": {
"bidder": {
"nid": "aaaaaaaaaaaaaaaaaaaaaaaa",
"nptnid": "1234"
}
}
}
]
},
"httpCalls": [
{
"expectedRequest": {
"method": "POST",
"uri": "http://hb-test.collectiveaudience.co/rtb/bid?nid=aaaaaaaaaaaaaaaaaaaaaaaa&nptnid=1234",
"headers": {
"Content-Type": [
"application/json;charset=utf-8"
],
"Accept": [
"application/json"
]
},
"body": {
"id": "request-id",
"device": {
"ua": "useragent",
"ip": "100.100.100.100",
"language": "en"
},
"imp": [
{
"id": "impression-id",
"banner": {
"w": 320,
"h": 50
},
"tagid": "tid",
"ext": {
"bidder": {
"nid": "aaaaaaaaaaaaaaaaaaaaaaaa",
"nptnid": "1234"
}
}
}
],
"site": {
"page": "test.com/page",
"domain": "test.com",
"cat": [
"IAB9-1"
],
"publisher": {
"id": "123456789"
}
},
"user": {
"id": "some-user"
},
"tmax": 1000
},
"impIDs": [
"impression-id"
]
},
"mockResponse": {
"status": 200,
"body": {
"id": "resp-id",
"seatbid": [
{
"bid": [
{
"id": "123456789",
"impid": "impression-id",
"price": 2,
"adm": "adm code",
"adomain": [
"testdomain.com"
],
"crid": "100",
"w": 320,
"h": 50,
"mtype": 1,
"ext": {
"prebid": {
"type": "banner"
}
}
}
]
}
],
"cur": "USD"
}
}
}
],
"expectedBidResponses": [
{
"bids": [
{
"bid": {
"id": "123456789",
"impid": "impression-id",
"price": 2,
"adm": "adm code",
"adomain": [
"testdomain.com"
],
"crid": "100",
"w": 320,
"h": 50,
"mtype": 1,
"ext": {
"prebid": {
"type": "banner"
}
}
},
"type": "banner"
}
]
}
]
}
Loading
Loading