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

[synacormedia] Add synacormedia adapter #1029

Merged
merged 9 commits into from
Oct 16, 2019
48 changes: 48 additions & 0 deletions adapters/synacormedia/params_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package synacormedia

import (
"encoding/json"
"testing"

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

// This file actually intends to test static/bidder-params/synacormedia.json
//
// These also validate the format of the external API: request.imp[i].ext.synacormedia

// TestValidParams makes sure that the synacormedia schema accepts all imp.ext fields which we intend to support.
func TestValidParams(t *testing.T) {
validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
if err != nil {
t.Fatalf("Failed to fetch the json-schemas. %v", err)
}

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

// TestInvalidParams makes sure that the synacormedia schema rejects all the imp.ext fields we don't support.
func TestInvalidParams(t *testing.T) {
validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
if err != nil {
t.Fatalf("Failed to fetch the json-schemas. %v", err)
}

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

var validParams = []string{
`{"seatId": "123"}`,
}

var invalidParams = []string{
`{"seatId": 123}`,
}
197 changes: 197 additions & 0 deletions adapters/synacormedia/synacormedia.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package synacormedia

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"
)

type SynacorMediaAdapter struct {
EndpointTemplate template.Template
}

type SyncEndpointTemplateParams struct {
SeatId string
}

type ReqExt struct {
SeatId string `json:"seatId"`
}

func NewSynacorMediaBidder(endpointTemplate string) adapters.Bidder {
syncTemplate, err := template.New("endpointTemplate").Parse(endpointTemplate)
if err != nil {
glog.Fatal("Unable to parse endpoint url template")
return nil
}
return &SynacorMediaAdapter{EndpointTemplate: *syncTemplate}
}

func (a *SynacorMediaAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
var errs []error
var bidRequests []*adapters.RequestData

adapterReq, errors := a.makeRequest(request)
if adapterReq != nil {
bidRequests = append(bidRequests, adapterReq)
}
errs = append(errs, errors...)

return bidRequests, errs
}

func (a *SynacorMediaAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) {
var errs []error
var validImps []openrtb.Imp
var re *ReqExt
var firstExtImp *openrtb_ext.ExtImpSynacormedia = nil

for _, imp := range request.Imp {
validImp, err := getExtImpObj(&imp)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does getExtImp(&imp) get executed twice in a row?

if err != nil {
errs = append(errs, err)
continue
}
validImps = append(validImps, imp)
if firstExtImp == nil {
firstExtImp = validImp
}
}

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

var err error

if firstExtImp == nil || firstExtImp.SeatId == "" {
return nil, append(errs, &errortypes.BadServerResponse{
Message: fmt.Sprintf("Impression missing seat id"),
})
}
re = &ReqExt{SeatId: firstExtImp.SeatId}

// create JSON Request Body
request.Imp = validImps
request.Ext, err = json.Marshal(re)
if err != nil {
return nil, append(errs, err)
}

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

// set Request Headers
headers := http.Header{}
headers.Add("Content-Type", "application/json;charset=utf-8")
guscarreon marked this conversation as resolved.
Show resolved Hide resolved
headers.Add("Accept", "application/json")

// create Request Uri
reqUri, err := a.buildEndpointURL(firstExtImp)
if err != nil {
return nil, append(errs, err)
}

return &adapters.RequestData{
Method: http.MethodPost,
Uri: reqUri,
Body: reqJSON,
Headers: headers,
}, errs
}

// Builds enpoint url based on adapter-specific pub settings from imp.ext
func (adapter *SynacorMediaAdapter) buildEndpointURL(params *openrtb_ext.ExtImpSynacormedia) (string, error) {
return macros.ResolveMacros(adapter.EndpointTemplate, macros.EndpointTemplateParams{Host: params.SeatId})
}

func getExtImpObj(imp *openrtb.Imp) (*openrtb_ext.ExtImpSynacormedia, error) {
var bidderExt adapters.ExtImpBidder
if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
return nil, &errortypes.BadInput{
Message: err.Error(),
}
}

var synacormediaExt openrtb_ext.ExtImpSynacormedia
if err := json.Unmarshal(bidderExt.Bidder, &synacormediaExt); err != nil {
return nil, &errortypes.BadInput{
Message: err.Error(),
}
}

return &synacormediaExt, nil
}

// MakeBids make the bids for the bid response.
func (a *SynacorMediaAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
const errorMessage string = "Unexpected status code: %d. Run with request.debug = 1 for more info"
switch {
case response.StatusCode == http.StatusNoContent:
return nil, nil
case response.StatusCode == http.StatusBadRequest:
return nil, []error{&errortypes.BadInput{
Message: fmt.Sprintf(errorMessage, response.StatusCode),
}}
case response.StatusCode != http.StatusOK:
return nil, []error{&errortypes.BadServerResponse{
Message: fmt.Sprintf(errorMessage, response.StatusCode),
}}
}

var bidResp openrtb.BidResponse

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

bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)

for _, sb := range bidResp.SeatBid {
for i := range sb.Bid {
var mediaType = getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp)
if mediaType != openrtb_ext.BidTypeBanner && mediaType != openrtb_ext.BidTypeVideo {
continue
}
bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
Bid: &sb.Bid[i],
BidType: mediaType,
})
}
}
return bidResponse, nil
}

func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType {
mediaType := openrtb_ext.BidTypeBanner
for _, imp := range imps {
if imp.ID == impId {
if imp.Banner != nil {
break
}
if imp.Video != nil {
mediaType = openrtb_ext.BidTypeVideo
break
}
if imp.Native != nil {
mediaType = openrtb_ext.BidTypeNative
break
}
if imp.Audio != nil {
mediaType = openrtb_ext.BidTypeAudio
break
}
}
}
return mediaType
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Function getMediaTypeForImp(impId string, imps []openrtb.Imp) might return openrtb_ext.BidTypeBanner even if imp.Banner == nil and imp.Video == nil. Other adapters overcome this issue by filtering out non-Banner and non-Video types somewhere inside MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo). I believe either filtering out non-Banner and non-Video imps, or modifying this function to return an error or something so we don't append this bid to the bidResponse array in MakeBids may solve this issue.

173 func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType {
174     mediaType := openrtb_ext.BidTypeBanner
175     for _, imp := range imps {
176         if imp.ID == impId {
177             if imp.Banner == nil && imp.Video != nil {
178                 mediaType = openrtb_ext.BidTypeVideo
179             }
180             return mediaType  //<- may return openrtb_ext.BidTypeBanner on an imp with imp.Banner==nil && imp.Video==nil element
181         }
182     }
183     return mediaType  //<- may return openrtb_ext.BidTypeBanner even if imp wasn't found in the imp list
184 }
synacormedia.go

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added filter.

	// filter out non banner and non video imps
	var validRequestImps []openrtb.Imp
	for _, imp := range internalRequest.Imp {
		if imp.Banner != nil || imp.Video != nil {
			validRequestImps = append(validRequestImps, imp)
		}
	}

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this for loop is exactly filtering out non banner and non video imps because if getMediaTypeForImp(sb.Bid[i].ImpID, validRequestImps) doesn't find a match in validRequestImps it will still assign the value openrtb_ext.BidTypeBanner to something that probably isn't. Do you think a better approach is this?

138 func (a *SynacorMediaAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
139     const errorMessage string = "Unexpected status code: %d. Run with request.debug = 1 for more info"
140     switch {
141     case response.StatusCode == http.StatusNoContent:
142         return nil, nil
143     case response.StatusCode == http.StatusBadRequest:
144         return nil, []error{&errortypes.BadInput{
145             Message: fmt.Sprintf(errorMessage, response.StatusCode),
146         }}
147     case response.StatusCode != http.StatusOK:
148         return nil, []error{&errortypes.BadServerResponse{
149             Message: fmt.Sprintf(errorMessage, response.StatusCode),
150         }}
151     }
152
153     var bidResp openrtb.BidResponse
154
155     if err := json.Unmarshal(response.Body, &bidResp); err != nil {
156         return nil, []error{err}
157     }
158
159     bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)
160
161  -  // filter out non banner and non video imps
162  -  var validRequestImps []openrtb.Imp
163  -  for _, imp := range internalRequest.Imp {
164  -      if imp.Banner != nil || imp.Video != nil {
165  -          validRequestImps = append(validRequestImps, imp)
166  -      }
167  -  }
168  -
169     for _, sb := range bidResp.SeatBid {
170         for i := range sb.Bid {
     +          mediaType = getMediaTypeForImp(sb.Bid[i].ImpID, validRequestImps)
     +          if mediaType != openrtb_ext.BidTypeBanner && mediaType != openrtb_ext.BidTypeVideo {
     +              continue
     +          }
171             bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
172                 Bid:     &sb.Bid[i],
173  -              BidType: getMediaTypeForImp(sb.Bid[i].ImpID, validRequestImps),
173                 BidType: mediaType,
174             })
175         }
176     }
177     return bidResponse, nil
178 }
179
180 func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType {
181     mediaType := openrtb_ext.BidTypeBanner
182     for _, imp := range imps {
183         if imp.ID == impId {
184  -          if imp.Banner == nil && imp.Video != nil {
185  -              mediaType = openrtb_ext.BidTypeVideo
     +          if imp.Banner != nil {
     +              break
     +          }
     +          if imp.Video != nil {
     +              mediaType = openrtb_ext.BidTypeVideo
     +              break
     +          }
     +          if imp.Native != nil {
     +              mediaType = openrtb_ext.BidTypeNative
     +              break
     +          }
     +          if imp.Audio != nil {
     +              mediaType = openrtb_ext.BidTypeAudio
     +              break
186             }
187  -          return mediaType
188         }
189     }
190     return mediaType
191 }
adapters/synacormedia/synacormedia.go

11 changes: 11 additions & 0 deletions adapters/synacormedia/synacormedia_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package synacormedia

import (
"testing"

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

func TestJsonSamples(t *testing.T) {
adapterstest.RunJSONBidderTest(t, "synacormediatest", NewSynacorMediaBidder("http://{{.Host}}.technoratimedia.com/openrtb/bids/{{.Host}}"))
}
110 changes: 110 additions & 0 deletions adapters/synacormedia/synacormediatest/exemplary/simple-banner.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
{
"mockBidRequest": {
"id": "test-request-id",
"imp": [
{
"id": "test-imp-id",
"banner": {
"format": [
{
"w": 300,
"h": 250
}
]
},
"ext": {
"bidder": {
"seatId": "1927"
}
}
}
]
},

"httpCalls": [
{
"expectedRequest": {
"uri": "http://1927.technoratimedia.com/openrtb/bids/1927",
"body": {
"id": "test-request-id",
"ext": {
"seatId": "1927"
},
"imp": [
{
"id":"test-imp-id",
"banner": {
"format": [
{"w":300,"h":250}
]
},
"ext": {
"bidder": {
"seatId": "1927"
}
}
}
]
}
},
"mockResponse": {
"status": 200,
"body": {
"id": "1",
"seatbid": [
{
"bid": [
{
"id": "test-request-id",
"impid": "test-imp-id",
"price": 2.69,
"adomain": [
"psacentral.org"
],
"cid": "mock-crid",
"crid": "mock-cid",
"ext": {
"prebid": {
"type": "banner"
}
}
}
],
"seat": "synacormedia"
}
],
"ext": {
"responsetimemillis": {
"synacormedia": 339
}
}
}
}
}
],
"expectedBidResponses": [
{
"currency": "USD",
"bids": [
{
"bid": {
"adomain": [
"psacentral.org"
],
"cid": "mock-crid",
"crid": "mock-cid",
"ext": {
"prebid": {
"type": "banner"
}
},
"id": "test-request-id",
"impid": "test-imp-id",
"price": 2.69
},
"type": "banner"
}
]
}
]
}
Loading