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: Mediasquare #3994

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
119 changes: 119 additions & 0 deletions adapters/mediasquare/mediasquare.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package mediasquare

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

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

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, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
var (
requestData []*adapters.RequestData
errs []error
)
if request == nil {
errs = append(errs, errorWritter("<MakeRequests> request", nil, true))
return nil, errs
}

msqParams := initMsqParams(request)
msqParams.Test = (request.Test == int8(1))
Copy link
Collaborator

Choose a reason for hiding this comment

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

I haven't seen this behavior before. Is this added because the msqParams.Test field is required by the MediaSquare backend to recognize the request as a test?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it is.

for _, imp := range request.Imp {
var (
bidderExt adapters.ExtImpBidder
msqExt openrtb_ext.ImpExtMediasquare
currentCode = msqParametersCodes{
AdUnit: imp.TagID,
AuctionId: request.ID,
BidId: imp.ID,
}
)

if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
errs = append(errs, errorWritter("<MakeRequests> imp[ext]", err, len(imp.Ext) == 0))
continue
}
if err := json.Unmarshal(bidderExt.Bidder, &msqExt); err != nil {
errs = append(errs, errorWritter("<MakeRequests> imp-bidder[ext]", err, len(bidderExt.Bidder) == 0))
continue
}
currentCode.Owner = msqExt.Owner
currentCode.Code = msqExt.Code

if ok := currentCode.setContent(imp); ok {
msqParams.Codes = append(msqParams.Codes, currentCode)
}
}

req, err := a.makeRequest(request, &msqParams)
if err != nil {
errs = append(errs, err)
} else if req != nil {
requestData = append(requestData, req)
}
return requestData, errs
}

func (a *adapter) makeRequest(request *openrtb2.BidRequest, msqParams *msqParameters) (requestData *adapters.RequestData, err error) {
var requestJsonBytes []byte
if msqParams == nil {
err = errorWritter("<makeRequest> msqParams", nil, true)
return
}
if requestJsonBytes, err = json.Marshal(msqParams); err == nil {
var headers http.Header = headerList
requestData = &adapters.RequestData{
Method: "POST",
Uri: a.endpoint,
Body: requestJsonBytes,
Headers: headers,
ImpIDs: openrtb_ext.GetImpIDs(request.Imp),
}
} else {
err = errorWritter("<makeRequest> json.Marshal", err, false)
}
return
}

func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
var (
bidderResponse *adapters.BidderResponse
errs []error
)
if response.StatusCode != http.StatusOK {
switch response.StatusCode {
case http.StatusBadRequest:
errs = []error{&errortypes.BadInput{Message: fmt.Sprintf("<MakeBids> Unexpected status code: %d.", response.StatusCode)}}
default:
errs = []error{&errortypes.BadServerResponse{
Message: fmt.Sprintf("<MakeBids> Unexpected status code: %d. Run with request.debug = 1 for more info.", response.StatusCode),
}}
}
return bidderResponse, errs
}

var msqResp msqResponse
if err := json.Unmarshal(response.Body, &msqResp); err != nil {
errs = []error{&errortypes.BadServerResponse{Message: fmt.Sprintf("<MakeBids> Bad server response: %s.", err.Error())}}
return bidderResponse, errs
}
bidderResponse = adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
msqResp.getContent(bidderResponse)
return bidderResponse, errs
}
135 changes: 135 additions & 0 deletions adapters/mediasquare/mediasquare_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package mediasquare

import (
"encoding/json"
"errors"
"fmt"
"net/http"
"testing"

"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/stretchr/testify/assert"
)

func TestMakeBids(t *testing.T) {
a, _ := Builder("mediasquare", config.Adapter{}, config.Server{})
tests := []struct {
// tests inputs
request *openrtb2.BidRequest
requestData *adapters.RequestData
// tests expected-results
response *adapters.ResponseData
bidderResponse *adapters.BidderResponse
errs []error
}{
{
request: &openrtb2.BidRequest{},
requestData: &adapters.RequestData{},

response: &adapters.ResponseData{StatusCode: http.StatusBadRequest},
bidderResponse: nil,
errs: []error{&errortypes.BadInput{
Message: fmt.Sprintf("<MakeBids> Unexpected status code: %d.", http.StatusBadRequest),
}},
},
{
request: &openrtb2.BidRequest{},
requestData: &adapters.RequestData{},

response: &adapters.ResponseData{StatusCode: 42},
bidderResponse: nil,
errs: []error{&errortypes.BadServerResponse{
Message: fmt.Sprintf("<MakeBids> Unexpected status code: %d. Run with request.debug = 1 for more info.", 42),
}},
},
{
request: &openrtb2.BidRequest{},
requestData: &adapters.RequestData{},

response: &adapters.ResponseData{StatusCode: http.StatusOK, Body: []byte("")},
bidderResponse: nil,
errs: []error{&errortypes.BadServerResponse{
Message: fmt.Sprint("<MakeBids> Bad server response: unexpected end of JSON input."),
}},
},
{
request: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1"}, {ID: "2"}, {ID: "3"}}},
requestData: &adapters.RequestData{},

response: &adapters.ResponseData{StatusCode: http.StatusOK, Body: []byte(`{"id":"id-ok"}`)},
bidderResponse: &adapters.BidderResponse{
Currency: "USD",
Bids: []*adapters.TypedBid{},
FledgeAuctionConfigs: nil,
},
errs: nil,
},
}

for index, test := range tests {
resp, errs := a.MakeBids(test.request, test.requestData, test.response)

errsVal, _ := json.Marshal(errs)
errsExp, _ := json.Marshal(test.errs)
assert.Equal(t, test.bidderResponse, resp, fmt.Sprintf("resp >> index: %d.", index))
assert.Equal(t, errsExp, errsVal, fmt.Sprintf("errs >> index: %d.", index))
}
}

func TestMakeRequest(t *testing.T) {
a, _ := Builder("mediasquare", config.Adapter{Endpoint: "edp-mediasquare"}, config.Server{})
tests := []struct {
// tests inputs
request *openrtb2.BidRequest
reqInfo *adapters.ExtraRequestInfo
// tests expected-results
result []*adapters.RequestData
errs []error
}{
{
request: &openrtb2.BidRequest{ID: "id-ok",
Imp: []openrtb2.Imp{
{ID: "0"},
{ID: "1", Ext: []byte(`{"id-1":"content-1"}`)},
{ID: "-42", Ext: []byte(`{"prebid":-42}`)},
{ID: "-1", Ext: []byte(`{"bidder":{}}`)},
{ID: "-0", Ext: []byte(`{"bidder":{"owner":"owner-ok","code":0}}`), Native: &openrtb2.Native{}},
{ID: "42", Ext: []byte(`{"bidder":{"owner":"owner-ok","code":"code-ok"}}`), Native: &openrtb2.Native{}},
},
},
reqInfo: &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "global-ok"},

result: []*adapters.RequestData{
{Method: "POST", Uri: "edp-mediasquare", Headers: headerList, ImpIDs: []string{"0", "1", "-42", "-1", "-0", "42"},
Body: []byte(`{"codes":[{"adunit":"","auctionid":"id-ok","bidid":"42","code":"code-ok","owner":"owner-ok","mediatypes":{"banner":null,"video":null,"native":{"title":null,"icon":null,"image":null,"clickUrl":null,"displayUrl":null,"privacyLink":null,"privacyIcon":null,"cta":null,"rating":null,"downloads":null,"likes":null,"price":null,"saleprice":null,"address":null,"phone":null,"body":null,"body2":null,"sponsoredBy":null,"sizes":null,"type":"native"}},"floor":{"*":{}}}],"gdpr":{"consent_required":false,"consent_string":""},"type":"pbs","dsa":"","tech":{"device":null,"app":null},"test":false}`)},
},
errs: []error{
errors.New("<MakeRequests> imp[ext]: is empty."),
errors.New("<MakeRequests> imp-bidder[ext]: is empty."),
errors.New("<MakeRequests> imp[ext]: json: cannot unmarshal number into Go struct field ExtImpBidder.prebid of type openrtb_ext.ExtImpPrebid"),
errors.New("<MakeRequests> imp-bidder[ext]: json: cannot unmarshal number into Go struct field ImpExtMediasquare.code of type string"),
},
},
}
for index, test := range tests {
result, errs := a.MakeRequests(test.request, test.reqInfo)

resultBytes, _ := json.Marshal(result)
expectedBytes, _ := json.Marshal(test.result)
assert.Equal(t, string(expectedBytes), string(resultBytes), fmt.Sprintf("result >> index: %d.", index))
assert.Equal(t, test.errs, errs, fmt.Sprintf("errs >> index: %d.", index))
}

// test reference : []error<MakeRequests> on empty request.
_, errs := a.MakeRequests(nil, nil)
assert.Equal(t, []error{errorWritter("<MakeRequests> request", nil, true)}, errs, "[]error<MakeRequests>")

var msqAdapter adapter
_, errNil := msqAdapter.makeRequest(nil, nil)
assert.Equal(t, errorWritter("<makeRequest> msqParams", nil, true), errNil, "error<makeRequest> errNil")
_, errChan := msqAdapter.makeRequest(nil, &msqParameters{DSA: make(chan int)})
assert.Equal(t, errorWritter("<makeRequest> json.Marshal", errors.New("json: unsupported type: chan int"), false), errChan, "error<makeRequest> errChan")
}
86 changes: 86 additions & 0 deletions adapters/mediasquare/parsers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package mediasquare

import (
"encoding/json"
"fmt"

"github.com/prebid/openrtb/v20/openrtb2"
)

// parserDSA: Struct used to extracts dsa content of a json.
type parserDSA struct {
DSA interface{} `json:"dsa,omitempty"`
}

// setContent: Unmarshal a []byte into the parserDSA struct.
func (parser *parserDSA) setContent(extJsonBytes []byte) error {
if len(extJsonBytes) > 0 {
if err := json.Unmarshal(extJsonBytes, parser); err != nil {
return errorWritter("<setContent(*parserDSA)> extJsonBytes", err, false)
}
return nil
}
return errorWritter("<setContent(*parserDSA)> extJsonBytes", nil, true)
}

// getValue: Returns the DSA value as a string, defaultly returns empty-string.
func (parser parserDSA) getValue(request *openrtb2.BidRequest) string {
if request == nil || request.Regs == nil {
return ""
}
parser.setContent(request.Regs.Ext)
if parser.DSA != nil {
return fmt.Sprint(parser.DSA)
}
return ""
}

// parserGDPR: Struct used to extract pair of GDPR/Consent of a json.
type parserGDPR struct {
GDPR interface{} `json:"gdpr,omitempty"`
Consent interface{} `json:"consent,omitempty"`
}

// setContent: Unmarshal a []byte into the parserGDPR struct.
func (parser *parserGDPR) setContent(extJsonBytes []byte) error {
if len(extJsonBytes) > 0 {
if err := json.Unmarshal(extJsonBytes, parser); err != nil {
return errorWritter("<setContent(*parserGDPR)> extJsonBytes", err, false)
}
return nil
}
return errorWritter("<setContent(*parserGDPR)> extJsonBytes", nil, true)
}

// value: Returns the consent or GDPR-string depending of the parserGDPR content, defaulty return empty-string.
func (parser *parserGDPR) value() string {
switch {
case parser.Consent != nil:
return fmt.Sprint(parser.Consent)
case parser.GDPR != nil:
return fmt.Sprint(parser.GDPR)
}
return ""
}

// getValue: Returns the consent or GDPR-string depending on the openrtb2.User content, defaultly returns empty-string.
func (parser parserGDPR) getValue(field string, request *openrtb2.BidRequest) string {
if request == nil {
return ""
}
switch {
case field == "consent_requirement" && request.Regs != nil:
if ptrInt8ToBool(request.Regs.GDPR) {
return "true"
}
return "false"
case field == "consent_string" && request.User != nil:
if len(request.User.Consent) > 0 {
return request.User.Consent
}
parser.setContent(request.User.Ext)
return parser.value()
default:
return ""
}
}
Loading
Loading