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: Readpeak #3610

Merged
merged 19 commits into from
Apr 25, 2024
Merged
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
51 changes: 51 additions & 0 deletions adapters/readpeak/params_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package readpeak

import (
"encoding/json"
"testing"

"github.com/prebid/prebid-server/v2/openrtb_ext"
)

func TestValidParams(t *testing.T) {
validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
if err != nil {
t.Fatalf("Failed to fetch the json schema. %v", err)
}

for _, p := range validParams {
if err := validator.Validate(openrtb_ext.BidderReadpeak, json.RawMessage(p)); err != nil {
t.Errorf("Schema rejected valid params: %s", p)
}
}
}

func TestInvalidParams(t *testing.T) {
validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
if err != nil {
t.Fatalf("Failed to fetch the json schema. %v", err)
}

for _, p := range invalidParams {
if err := validator.Validate(openrtb_ext.BidderReadpeak, json.RawMessage(p)); err == nil {
t.Errorf("Schema allowed invalid params: %s", p)
}
}
}

var validParams = []string{
`{"publisherId": ""}`,
`{"publisherId": "Some Pub ID", "siteId": ""}`,
`{"publisherId": "Some Pub ID", "siteId": "Some Site ID"}`,
`{"publisherId": "Some Pub ID", "siteId": "Some Site ID", "bidfloor": 1.5}`,
`{"publisherId": "Some Pub ID", "siteId": "Some Site ID", "bidfloor": 1.5, "tagId": "Some tag ID"}`,
}

var invalidParams = []string{
`{"publisherId": 42}`,
`{"publisherId": "42", "siteId": 42}`,
`{"siteId": 42}`,
`{"publisherId": "Some Pub ID", "siteId": 42}`,
`{"publisherId": "Some Pub ID", "siteId": "Some Site ID", bidfloor: "1.5"}`,
`{"publisherId": "Some Pub ID", "siteId": "Some Site ID", bidfloor: 1.5, tagId: 1}`,
}
161 changes: 161 additions & 0 deletions adapters/readpeak/readpeak.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package readpeak

import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"

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

type adapter struct {
endpoint string
}

// Builder builds a new instance of the Readpeak adapter for the given bidder with the given config.
func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
return &adapter{
endpoint: config.Endpoint,
}, nil
}

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

requestCopy := *request
var rpExt openrtb_ext.ImpExtReadpeak
var imps []openrtb2.Imp
for i := 0; i < len(requestCopy.Imp); i++ {
var impExt adapters.ExtImpBidder
if err := json.Unmarshal(requestCopy.Imp[i].Ext, &impExt); err != nil {
errors = append(errors, err)
continue
}
if err := json.Unmarshal(impExt.Bidder, &rpExt); err != nil {
errors = append(errors, err)
continue
}
imp := requestCopy.Imp[i]
if rpExt.TagId != "" {
imp.TagID = rpExt.TagId
}
if rpExt.Bidfloor != 0 {
imp.BidFloor = rpExt.Bidfloor
}
imps = append(imps, imp)
}

if len(imps) == 0 {
err := &errortypes.BadInput{
Message: fmt.Sprintf("Failed to find compatible impressions for request %s", requestCopy.ID),
}
return nil, []error{err}
}
requestCopy.Imp = imps
publisher := &openrtb2.Publisher{
ID: rpExt.PublisherId,
}

if requestCopy.Site != nil {
siteCopy := *request.Site
if rpExt.SiteId != "" {
siteCopy.ID = rpExt.SiteId
}
siteCopy.Publisher = publisher
requestCopy.Site = &siteCopy
} else if requestCopy.App != nil {
appCopy := *request.App
if rpExt.SiteId != "" {
appCopy.ID = rpExt.SiteId
}
appCopy.Publisher = publisher
requestCopy.App = &appCopy
}

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

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

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

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

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

if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil {
return nil, []error{err}
}

var response openrtb2.BidResponse
if err := json.Unmarshal(responseData.Body, &response); err != nil {
return nil, []error{err}
}

bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
if len(response.Cur) != 0 {
bidResponse.Currency = response.Cur
}
var errors []error
for _, seatBid := range response.SeatBid {
for i := range seatBid.Bid {
bidType, err := getMediaTypeForBid(seatBid.Bid[i])
if err != nil {
errors = append(errors, err)
continue
}
resolveMacros(&seatBid.Bid[i])
bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
Bid: &seatBid.Bid[i],
BidType: bidType,
BidMeta: getBidMeta(&seatBid.Bid[i]),
})
}
}
return bidResponse, errors
}

func resolveMacros(bid *openrtb2.Bid) {
if bid != nil {
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)
bid.BURL = strings.Replace(bid.BURL, "${AUCTION_PRICE}", price, -1)
}
}
func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) {
switch bid.MType {
case openrtb2.MarkupBanner:
return openrtb_ext.BidTypeBanner, nil
case openrtb2.MarkupNative:
return openrtb_ext.BidTypeNative, nil
default:
return "", fmt.Errorf("Failed to find impression type \"%s\"", bid.ImpID)
}
}

func getBidMeta(bid *openrtb2.Bid) *openrtb_ext.ExtBidPrebidMeta {
return &openrtb_ext.ExtBidPrebidMeta{
AdvertiserDomains: bid.ADomain,
}
}
21 changes: 21 additions & 0 deletions adapters/readpeak/readpeak_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package readpeak

import (
"testing"

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

func TestJsonSamples(t *testing.T) {
bidder, buildErr := Builder(openrtb_ext.BidderReadpeak, config.Adapter{
Endpoint: "https://dsp.readpeak.com/header/prebid"},
config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})

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

adapterstest.RunJSONBidderTest(t, "readpeaktest", bidder)
}
130 changes: 130 additions & 0 deletions adapters/readpeak/readpeaktest/exemplary/banner.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
{
"mockBidRequest": {
"id": "req-id-banner",
"imp": [
{
"id": "imp-id-banner",
"banner": {
"format": [
{
"w": 640,
"h": 400
},
{
"w": 320,
"h": 300
}
]
},
"ext": {
"bidder": {
"siteId": "site-id-b",
"publisherId": "publisher-id-b",
"tagId": "tag-id-b",
"bidfloor": 5.0
}
}
}
],
"site": {
"id": "some-site-id-b",
"domain": "http://test.com",
"page": "http://test.com/test",
"publisher": {
"id": "some-pub-id-b"
}
}
},
"httpCalls": [
{
"expectedRequest": {
"uri": "https://dsp.readpeak.com/header/prebid",
"body": {
"id": "req-id-banner",
"imp": [
{
"id": "imp-id-banner",
"tagid": "tag-id-b",
"bidfloor": 5.0,
"banner": {
"format": [
{
"w": 640,
"h": 400
},
{
"w": 320,
"h": 300
}
]
},
"ext": {
"bidder": {
"siteId": "site-id-b",
"publisherId": "publisher-id-b",
"tagId": "tag-id-b",
"bidfloor": 5.0
}
}
}
],
"site": {
"id": "site-id-b",
"publisher": {
"id": "publisher-id-b"
},
"domain": "http://test.com",
"page": "http://test.com/test"
}
},
"impIDs":["imp-id-banner"]
},
"mockResponse": {
"status": 200,
"body": {
"id": "req-id-banner",
"seatbid": [
{
"seat": "bid",
"bid": [
{
"id": "test-bid-id-banner",
"impid": "imp-id-banner",
"price": 0.500000,
"adm": "<html><body>some-test-ad-banner<img src='http://test.com/burl?p=${AUCTION_PRICE}'></body></html>",
"crid": "123",
"w": 640,
"h": 400,
"burl": "http://test.com/burl?p=${AUCTION_PRICE}",
"mtype": 1
}
]
}
],
"cur": "USD"
}
}
}
],
"expectedBidResponses": [
{
"currency": "USD",
"bids": [
{
"bid": {
"id": "test-bid-id-banner",
"impid": "imp-id-banner",
"price": 0.5,
"adm": "<html><body>some-test-ad-banner<img src='http://test.com/burl?p=0.5'></body></html>",
"crid": "123",
"w": 640,
"h": 400,
"burl": "http://test.com/burl?p=0.5",
"mtype": 1
},
"type": "banner"
}
]
}
]
}
Loading
Loading