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

New Adapter: Bidtheatre #4069

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
112 changes: 112 additions & 0 deletions adapters/bidtheatre/bidtheatre.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package bidtheatre

import (
"fmt"
"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"
"net/http"
"strconv"
"strings"
)

type adapter struct {
endpoint string
}

// Builder builds a new instance of the Bidtheatre adapter for the given bidder with the given config.
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) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {

andreasgreen marked this conversation as resolved.
Show resolved Hide resolved
requestJSON, err := jsonutil.Marshal(request)
if err != nil {
return nil, []error{err}
}

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

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

func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) {
if bid.Ext != nil {
andreasgreen marked this conversation as resolved.
Show resolved Hide resolved
var bidExt openrtb_ext.ExtBid
err := jsonutil.Unmarshal(bid.Ext, &bidExt)
andreasgreen marked this conversation as resolved.
Show resolved Hide resolved
if err == nil && bidExt.Prebid != nil {
return openrtb_ext.ParseBidType(string(bidExt.Prebid.Type))

Choose a reason for hiding this comment

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

Consider this as a suggestion. Prebid server expects the media type to be explicitly set in the adapter response. Therefore, recommends implementing a pattern where the adapter server sets the MType field in the response to accurately determine the media type for the impression.

}
}

return "", &errortypes.BadServerResponse{
Message: fmt.Sprintf("Failed to parse impression \"%s\" mediatype", bid.ImpID),
}
}

func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {

andreasgreen marked this conversation as resolved.
Show resolved Hide resolved
if responseData.StatusCode == http.StatusNoContent {
andreasgreen marked this conversation as resolved.
Show resolved Hide resolved
return nil, nil
}

if responseData.StatusCode == http.StatusBadRequest {
err := &errortypes.BadInput{
Message: "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.",
}
return nil, []error{err}
}

if responseData.StatusCode != http.StatusOK {
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 response openrtb2.BidResponse
if err := jsonutil.Unmarshal(responseData.Body, &response); err != nil {
return nil, []error{err}
}

bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
bidResponse.Currency = response.Cur
var errors []error
for _, seatBid := range response.SeatBid {
for i, bid := range seatBid.Bid {
andreasgreen marked this conversation as resolved.
Show resolved Hide resolved
resolveMacros(&seatBid.Bid[i])
bidType, err := getMediaTypeForBid(bid)
if err != nil {
errors = append(errors, err)
andreasgreen marked this conversation as resolved.
Show resolved Hide resolved
continue
}
bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
Bid: &seatBid.Bid[i],
BidType: bidType,
})
}
}
return bidResponse, nil
}

func resolveMacros(bid *openrtb2.Bid) {
if bid == nil {
return
}
price := strconv.FormatFloat(bid.Price, 'f', -1, 64)
bid.NURL = strings.Replace(bid.NURL, "${AUCTION_PRICE}", price, -1)
bid.AdM = strings.Replace(bid.AdM, "${AUCTION_PRICE}", price, -1)
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we handle the macro replacement directly within the adapter? Is there a specific reason why it's being done here instead?

Copy link
Contributor

Choose a reason for hiding this comment

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

This function is private to the adapter. What exactly do you mean?

Copy link
Contributor

Choose a reason for hiding this comment

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

I want to know reason to replace macro here instead of replacing at adapter side while sending out the response

Copy link
Author

Choose a reason for hiding this comment

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

@pm-nikhil-vaidya :

I'm sorry but I don't fully understand what I've done wrong here. What I tried to do was to fulfill this recommendation

We recommend resolving creative OpenRTB macros in your adapter. Otherwise, AUCTION_PRICE will eventually get resolved by the Prebid Universal Creative, but by then the bid price will be in the ad server currency and quantized by the price granularity.

from here: https://docs.prebid.org/prebid-server/developers/add-new-bidder-go.html

and in general just make sure that all occurrences of ${AUCTION_PRICE} are accurately replaced with the bid/winning price.

How do you suggest this is better done?

}
90 changes: 90 additions & 0 deletions adapters/bidtheatre/bidtheatre_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package bidtheatre
andreasgreen marked this conversation as resolved.
Show resolved Hide resolved

import (
"github.com/prebid/openrtb/v20/openrtb2"
"github.com/prebid/prebid-server/v3/adapters/adapterstest"
"github.com/prebid/prebid-server/v3/config"
"github.com/prebid/prebid-server/v3/openrtb_ext"
"strings"
"testing"
)

func TestJsonSamples(t *testing.T) {
bidder, buildErr := Builder(openrtb_ext.BidderBidtheatre, config.Adapter{
Endpoint: "http://any.url"},
config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})

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

adapterstest.RunJSONBidderTest(t, "bidtheatretest", bidder)
}

func TestGetBidTypes(t *testing.T) {
mockBid := openrtb2.Bid{
ID: "mock-bid-id",
ImpID: "mock-imp-id",
Price: 1.23,
AdID: "mock-ad-id",
CrID: "mock-cr-id",
DealID: "mock-deal-id",
W: 980,
H: 240,
Ext: []byte(`{"prebid": {"type": "banner"}}`),
BURL: "https://example.com/win-notify",
Cat: []string{"IAB1"},
}

actualBidTypeValue, _ := getMediaTypeForBid(mockBid)

if actualBidTypeValue != openrtb_ext.BidTypeBanner {
t.Errorf("Expected Bid Type value was: %v, actual value is: %v", openrtb_ext.BidTypeBanner, actualBidTypeValue)
}

mockBid.Ext = []byte(`{"prebid": {"type": "video"}}`)

actualBidTypeValue, _ = getMediaTypeForBid(mockBid)

if actualBidTypeValue != openrtb_ext.BidTypeVideo {
t.Errorf("Expected Bid Type value was: %v, actual value is: %v", openrtb_ext.BidTypeVideo, actualBidTypeValue)
}

}

func TestReplaceMacros(t *testing.T) {
mockBid := openrtb2.Bid{
ID: "mock-bid-id",
ImpID: "mock-imp-id",
Price: 1.23,
AdID: "mock-ad-id",
CrID: "mock-cr-id",
DealID: "mock-deal-id",
W: 980,
H: 240,
Ext: []byte(`{"prebid": {"type": "banner"}}`),
BURL: "https://example.com/win-notify",
Cat: []string{"IAB1"},
AdM: "<script type=\"text/javascript\">\n var uri = 'https://adsby.bidtheatre.com/imp?z=27025&a=1915538&so=1&ex=36&eb=3672319&xs=940698616&wp=${AUCTION_PRICE}&su=unknown&es=prebid.org&tag=unspec_980_300&kuid=eab9340e-8731-4027-9ada-57b554c75501&dealId=&mp=&ma=eyJjZCI6ZmFsc2UsInN0IjoxLCJtbGF0Ijo1OS4zMjkzLCJhZGMiOi0xLCJtb3JnIjoidGVsaWEgbmV0d29yayBzZXJ2aWNlcyIsIm1sc2NvcmUiOjAuMDczMTkwMTc0OTk2ODUyODcsIm16aXAiOiIxMTEyMCIsImJpcCI6IjgxLjIyNy44Mi4yOCIsImFnaWQiOjM1NjI3MDIsIm1sbW9kZWwiOiJtYXN0ZXJfbWxfY2xrXzU0MyIsInVhIjoiY3VybFwvNy44Ny4wIiwiYnJyZSI6ImFiIiwibWxvbiI6MTguMDY4NiwibXJlZ2lvbiI6ImFiIiwiZHQiOjgsImJyY28iOiJzd2UiLCJtY2l0eSI6InN0b2NraG9sbSIsImJyY2kiOiJzdG9ja2hvbG0iLCJwYWdldXJsIjoicHJlYmlkLm9yZyIsImltcGlkIjoieDM2X2FzeC1iLXMyXzc4MTc1MTk2NTcxMDMzNjUyMDciLCJtY291bnRyeSI6InN3ZSIsInRzIjoxNzMyMTEyNzMxNjgyfQ%3D%3D&usersync=1&cd=0&impId=x36_asx-b-s2_7817519657103365207&gdpr=0&gdpr_consent=&cb0=&rnd=' + new String (Math.random()).substring (2, 11);\n document.write('<sc'+'ript type=\"text/javascript\" src=\"'+uri+'\" charset=\"ISO-8859-1\"></sc'+'ript>');\n</script>",
NURL: "https://adsby.bidtheatre.com/video?z=27025;a=1922926;ex=36;es=prebid.org;eb=3672319;xs=940698616;so=1;tag=unspec_640_360;kuid=1d10dda6-740d-4386-94a0-7042b2ad2a66;wp=${AUCTION_PRICE};su=unknown;iab=vast2;dealId=;ma=eyJjZCI6ZmFsc2UsInN0IjozLCJtbGF0Ijo1OS4zMjkzLCJhZGMiOi0xLCJtb3JnIjoidGVsaWEgbmV0d29yayBzZXJ2aWNlcyIsIm1sc2NvcmUiOjkuNTY5ODA3NDQzNzY3Nzg2RS00LCJtemlwIjoiMTExMjAiLCJiaXAiOiI4MS4yMjcuODIuMjgiLCJhZ2lkIjozNTYyNzAyLCJtbG1vZGVsIjoibWFzdGVyX21sX2Nsa181NDMiLCJ1YSI6ImN1cmxcLzcuODcuMCIsImJycmUiOiJhYiIsIm1sb24iOjE4LjA2ODYsIm1yZWdpb24iOiJhYiIsImR0Ijo4LCJicmNvIjoic3dlIiwibWNpdHkiOiJzdG9ja2hvbG0iLCJicmNpIjoic3RvY2tob2xtIiwicGFnZXVybCI6InByZWJpZC5vcmciLCJpbXBpZCI6IngzNl9hc3gtYi1zMV8yNTY5OTI0ODYzMjY2ODA4OTM2IiwibWNvdW50cnkiOiJzd2UiLCJ0cyI6MTczMjA5NjgyNjg5OH0%3D;cd=0;cb0=;impId=x36_asx-b-s1_2569924863266808936;gdpr=0;gdpr_consent=",
}

resolveMacros(&mockBid)

if !strings.Contains(mockBid.AdM, "&wp=1.23&") {
t.Errorf("AdM ${AUCTION_PRICE} not correctly replaced")
}

if strings.Contains(mockBid.AdM, "${AUCTION_PRICE}") {
t.Errorf("AdM ${AUCTION_PRICE} not correctly replaced")
}

if !strings.Contains(mockBid.NURL, ";wp=1.23;") {
t.Errorf("NURL ${AUCTION_PRICE} not correctly replaced")
}

if strings.Contains(mockBid.NURL, "${AUCTION_PRICE}") {
t.Errorf("NURL ${AUCTION_PRICE} not correctly replaced")
}

}
128 changes: 128 additions & 0 deletions adapters/bidtheatre/bidtheatretest/exemplary/simple-banner.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
{
"mockBidRequest": {
"id": "test-request-id",
"imp": [
{
"id": "test-imp-id",
"banner": {
"format": [
{
"w": 980,
"h": 240
},
{
"w": 980,
"h": 300
}
]
},
"ext": {
"bidder": {
"publisherId": "73b20b3a-12a0-4869-b54e-8d42b55786ee"
}
}
}
],
"site": {
"page": "prebid.org"
},
"device": {
"ip": "81.227.82.28"
}
},

"httpCalls": [
{
"expectedRequest": {
"uri": "http://any.url",
"body": {
"id": "test-request-id",
"imp": [
{
"id": "test-imp-id",
"banner": {
"format": [
{
"w": 980,
"h": 240
},
{
"w": 980,
"h": 300
}
]
},
"ext": {
"bidder": {
"publisherId": "73b20b3a-12a0-4869-b54e-8d42b55786ee"
}
}
}
],
"site": {
"page": "prebid.org"
},
"device": {
"ip": "81.227.82.28"
}
},
"impIDs":["test-imp-id"]
},
"mockResponse": {
"status": 200,
"body": {
"cur": "USD",
"seatbid": [
{
"seat": "5",
"bid": [
{
"id": "test-imp-id",
"impid": "test-imp-id",
"price": 5.08712911605835,
"adm": "<script type=\"text\/javascript\">\n var uri = 'https:\/\/adsby.bidtheatre.com\/imp?z=27025&a=1915538&so=1&ex=36&eb=3672319&xs=940698616&wp=${AUCTION_PRICE}&su=unknown&es=prebid.org&tag=unspec_980_300&kuid=eab9340e-8731-4027-9ada-57b554c75501&dealId=&mp=&ma=eyJjZCI6ZmFsc2UsInN0IjoxLCJtbGF0Ijo1OS4zMjkzLCJhZGMiOi0xLCJtb3JnIjoidGVsaWEgbmV0d29yayBzZXJ2aWNlcyIsIm1sc2NvcmUiOjAuMDczMTkwMTc0OTk2ODUyODcsIm16aXAiOiIxMTEyMCIsImJpcCI6IjgxLjIyNy44Mi4yOCIsImFnaWQiOjM1NjI3MDIsIm1sbW9kZWwiOiJtYXN0ZXJfbWxfY2xrXzU0MyIsInVhIjoiY3VybFwvNy44Ny4wIiwiYnJyZSI6ImFiIiwibWxvbiI6MTguMDY4NiwibXJlZ2lvbiI6ImFiIiwiZHQiOjgsImJyY28iOiJzd2UiLCJtY2l0eSI6InN0b2NraG9sbSIsImJyY2kiOiJzdG9ja2hvbG0iLCJwYWdldXJsIjoicHJlYmlkLm9yZyIsImltcGlkIjoieDM2X2FzeC1iLXMyXzc4MTc1MTk2NTcxMDMzNjUyMDciLCJtY291bnRyeSI6InN3ZSIsInRzIjoxNzMyMTEyNzMxNjgyfQ%3D%3D&usersync=1&cd=0&impId=x36_asx-b-s2_7817519657103365207&gdpr=0&gdpr_consent=&cb0=&rnd=' + new String (Math.random()).substring (2, 11);\n document.write('<sc'+'ript type=\"text\/javascript\" src=\"'+uri+'\" charset=\"ISO-8859-1\"><\/sc'+'ript>');\n<\/script>",
"adid": "1915538",
"cid": "c154375",
"crid": "1915538",
"w": 980,
"h": 240,
"ext": {
"prebid": {
"type": "banner"
}
}
}
]
}
]
}
}
}
],
"expectedBidResponses": [
{
"bids":[
{
"currency": "USD",
"bid": {
"id": "test-imp-id",
"impid": "test-imp-id",
"price": 5.08712911605835,
"adm": "<script type=\"text\/javascript\">\n var uri = 'https:\/\/adsby.bidtheatre.com\/imp?z=27025&a=1915538&so=1&ex=36&eb=3672319&xs=940698616&wp=5.08712911605835&su=unknown&es=prebid.org&tag=unspec_980_300&kuid=eab9340e-8731-4027-9ada-57b554c75501&dealId=&mp=&ma=eyJjZCI6ZmFsc2UsInN0IjoxLCJtbGF0Ijo1OS4zMjkzLCJhZGMiOi0xLCJtb3JnIjoidGVsaWEgbmV0d29yayBzZXJ2aWNlcyIsIm1sc2NvcmUiOjAuMDczMTkwMTc0OTk2ODUyODcsIm16aXAiOiIxMTEyMCIsImJpcCI6IjgxLjIyNy44Mi4yOCIsImFnaWQiOjM1NjI3MDIsIm1sbW9kZWwiOiJtYXN0ZXJfbWxfY2xrXzU0MyIsInVhIjoiY3VybFwvNy44Ny4wIiwiYnJyZSI6ImFiIiwibWxvbiI6MTguMDY4NiwibXJlZ2lvbiI6ImFiIiwiZHQiOjgsImJyY28iOiJzd2UiLCJtY2l0eSI6InN0b2NraG9sbSIsImJyY2kiOiJzdG9ja2hvbG0iLCJwYWdldXJsIjoicHJlYmlkLm9yZyIsImltcGlkIjoieDM2X2FzeC1iLXMyXzc4MTc1MTk2NTcxMDMzNjUyMDciLCJtY291bnRyeSI6InN3ZSIsInRzIjoxNzMyMTEyNzMxNjgyfQ%3D%3D&usersync=1&cd=0&impId=x36_asx-b-s2_7817519657103365207&gdpr=0&gdpr_consent=&cb0=&rnd=' + new String (Math.random()).substring (2, 11);\n document.write('<sc'+'ript type=\"text\/javascript\" src=\"'+uri+'\" charset=\"ISO-8859-1\"><\/sc'+'ript>');\n<\/script>",
"adid": "1915538",
"cid": "c154375",
"crid": "1915538",
"w": 980,
"h": 240,
"ext": {
"prebid": {
"type": "banner"
}
}
},
"type": "banner"
}
]
}
]
}
Loading
Loading