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 SmartRTB adapter #1071

Merged
merged 13 commits into from
Jan 16, 2020
189 changes: 189 additions & 0 deletions adapters/smartrtb/smartrtb.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package smartrtb

import (
"encoding/json"
"fmt"
"net/http"
"text/template"

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

// Base adapter structure.
type SmartRTBAdapter struct {
EndpointTemplate template.Template
}

// Bid request extension appended to downstream request.
// PubID are non-empty iff request.{App,Site} or
// request.{App,Site}.Publisher are nil, respectively.
type bidRequestExt struct {
PubID string `json:"pub_id,omitempty"`
ZoneID string `json:"zone_id,omitempty"`
ForceBid bool `json:"force_bid,omitempty"`
}

// bidExt.CreativeType values.
const (
creativeTypeBanner string = "BANNER"
creativeTypeVideo = "VIDEO"
creativeTypeNative = "NATIVE"
creativeTypeAudio = "AUDIO"
)

// Bid response extension from downstream.
type bidExt struct {
CreativeType string `json:"format"`
}

func NewSmartRTBBidder(endpointTemplate string) adapters.Bidder {
template, err := template.New("endpointTemplate").Parse(endpointTemplate)
if err != nil {
glog.Fatal("Template URL error")
return nil
}
return &SmartRTBAdapter{EndpointTemplate: *template}
}

func (adapter *SmartRTBAdapter) buildEndpointURL(pubID string) (string, error) {
endpointParams := macros.EndpointTemplateParams{PublisherID: pubID}
return macros.ResolveMacros(adapter.EndpointTemplate, endpointParams)
}

func parseExtImp(dst *bidRequestExt, imp *openrtb.Imp) error {
var ext adapters.ExtImpBidder
if err := json.Unmarshal(imp.Ext, &ext); err != nil {
return adapters.BadInput(err.Error())
}

var src openrtb_ext.ExtImpSmartRTB
if err := json.Unmarshal(ext.Bidder, &src); err != nil {
return adapters.BadInput(err.Error())
}

if dst.PubID == "" {
dst.PubID = src.PubID
}

if src.ZoneID != "" {
imp.TagID = src.ZoneID
}
return nil
}

func (s *SmartRTBAdapter) MakeRequests(brq *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
var imps []openrtb.Imp
var err error
ext := bidRequestExt{}
nrImps := len(brq.Imp)
errs := make([]error, 0, nrImps)

for i := 0; i < nrImps; i++ {
imp := brq.Imp[i]
if imp.Banner == nil && imp.Video == nil {
continue
}

err = parseExtImp(&ext, &imp)
if err != nil {
errs = append(errs, err)
continue
}

imps = append(imps, imp)
}

if len(imps) == 0 {
return nil, errs
}

if ext.PubID == "" {
return nil, append(errs, adapters.BadInput("Cannot infer publisher ID from bid ext"))
}

brq.Ext, err = json.Marshal(ext)
if err != nil {
return nil, append(errs, err)
}

brq.Imp = imps

rq, err := json.Marshal(brq)
if err != nil {
return nil, append(errs, err)
}

url, err := s.buildEndpointURL(ext.PubID)
if err != nil {
return nil, append(errs, err)
}

headers := http.Header{}
headers.Add("Content-Type", "application/json;charset=utf-8")
headers.Add("Accept", "application/json")
headers.Add("x-openrtb-version", "2.5")
return []*adapters.RequestData{{
Method: "POST",
Uri: url,
Body: rq,
Headers: headers,
}}, errs
}

func (s *SmartRTBAdapter) MakeBids(
brq *openrtb.BidRequest, drq *adapters.RequestData,
rs *adapters.ResponseData,
) (*adapters.BidderResponse, []error) {
if rs.StatusCode == http.StatusNoContent {
return nil, nil
} else if rs.StatusCode == http.StatusBadRequest {
return nil, []error{adapters.BadInput("Invalid request.")}
} else if rs.StatusCode != http.StatusOK {
return nil, []error{&errortypes.BadServerResponse{
Message: fmt.Sprintf("Unexpected HTTP status %d.", rs.StatusCode),
}}
}

var brs openrtb.BidResponse
if err := json.Unmarshal(rs.Body, &brs); err != nil {
return nil, []error{err}
}

rv := adapters.NewBidderResponseWithBidsCapacity(5)
for _, seat := range brs.SeatBid {
for i := range seat.Bid {
var ext bidExt
if err := json.Unmarshal(seat.Bid[i].Ext, &ext); err != nil {
return nil, []error{&errortypes.BadServerResponse{
Message: "Invalid bid extension from endpoint.",
}}
}

var btype openrtb_ext.BidType
switch ext.CreativeType {
case creativeTypeBanner:
btype = openrtb_ext.BidTypeBanner
case creativeTypeVideo:
btype = openrtb_ext.BidTypeVideo
default:
return nil, []error{&errortypes.BadServerResponse{
Message: fmt.Sprintf("Unsupported creative type %s.",
ext.CreativeType),
}}
}

seat.Bid[i].Ext = nil

rv.Bids = append(rv.Bids, &adapters.TypedBid{
Bid: &seat.Bid[i],
BidType: btype,
})
}
}
return rv, nil
}
11 changes: 11 additions & 0 deletions adapters/smartrtb/smartrtb_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package smartrtb

import (
"testing"

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

func TestJsonSamples(t *testing.T) {
adapterstest.RunJSONBidderTest(t, "smartrtbtest", NewSmartRTBBidder("http://market-east.smrtb.com/json/publisher/rtb?pubid=test"))
}
134 changes: 134 additions & 0 deletions adapters/smartrtb/smartrtbtest/exemplary/banner.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
{
"mockBidRequest": {
"id": "abc",
"site": {
"page": "prebid.org"
},
"imp": [
{
"id": "imp123",
"banner": {
"format": [
{
"w": 300,
"h": 250
}
],
"w": 300,
"h": 250
},
"ext": {
"bidder": {
"pub_id": "test",
"zone_id": "N4zTDq3PPEHBIODv7cXK",
"force_bid": true
}
}
}
]
},

"httpCalls": [
{
"expectedRequest": {
"uri": "http://market-east.smrtb.com/json/publisher/rtb?pubid=test",
"body":{
"id": "abc",
"site": {
"page": "prebid.org"
},
"imp": [{
"id": "imp123",
"tagid": "N4zTDq3PPEHBIODv7cXK",
"banner": {
"format": [{
"w": 300,
"h": 250
}],
"w": 300,
"h": 250
},
"ext": {
"bidder": {
"pub_id": "test",
"zone_id": "N4zTDq3PPEHBIODv7cXK",
"force_bid": true
}
}
}],
"ext": {
"pub_id": "test"
}
}
},
"mockResponse": {
"status": 200,
"body": {
"id": "abc",
"seatbid": [
{
"bid": [
{
"adm": "<b>hi</b>",
"crid": "test_banner_crid",
"cid": "test_cid",
"impid": "imp123",
"id": "1",
"price": 0.01,
"ext": {
"format": "BANNER"
}
}
]
},
{
"bid": [
{
"adm": "<VAST></VAST>",
"crid": "test_video_crid",
"cid": "test_cid",
"impid": "imp123",
"id": "2",
"price": 0.01,
"ext": {
"format": "VIDEO"
}
}
]
}
]
}
}
}
],

"expectedBidResponses": [
{
"currency": "USD",
"bids": [
{
"bid": {
"adm": "<b>hi</b>",
"crid": "test_banner_crid",
"cid": "test_cid",
"impid": "imp123",
"price": 0.01,
"id": "1"
},
"type": "banner"
},
{
"bid": {
"adm": "<VAST></VAST>",
"crid": "test_video_crid",
"cid": "test_cid",
"impid": "imp123",
"price": 0.01,
"id": "2"
},
"type": "video"
}
]
}
]
}
Loading