Skip to content

Commit

Permalink
Add SmartRTB adapter (#1071)
Browse files Browse the repository at this point in the history
  • Loading branch information
evanmsmrtb authored and SyntaxNode committed Jan 16, 2020
1 parent 148b1f1 commit 7d9b1ec
Show file tree
Hide file tree
Showing 29 changed files with 1,266 additions and 0 deletions.
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

0 comments on commit 7d9b1ec

Please sign in to comment.