-
Notifications
You must be signed in to change notification settings - Fork 770
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
base: master
Are you sure you want to change the base?
New Adapter: Mediasquare #3994
Changes from 8 commits
b4a4a3f
1e3b225
1c8be46
f36098a
6051fdb
14677dd
f49cc00
355ddb0
c7221a2
7a2966b
3a1f90d
747bff3
8de23e9
0a0e2b2
01c1674
71d2d6d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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)) | ||
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why You have two makeRequest func()? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess you mean why we do have a Can you elaborate on the the reason(s) why the files There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will confirm with the team, but I think it's totally ok to have extra files (within the adapter directory) to make code more readable and structured. This adapter accepts and returns it's own format, not OpenRTB format. Having all the structures and converters in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed. For simple adapters a single file should suffice. However, in this case, the adapter is rather complex so I do appreciate and prefer the code organization across multiple files. |
||
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 | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would you consider to clean up error handling using I also noticed an interesting thing. When I run the request to mediasquare and bids are not returned - you still return http 200 with error message. This is not typical behavior. If bids are not returned we usually get http 204. If this is something you cannot change, please consider to add a check for empty |
||
|
||
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 | ||
} |
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") | ||
} |
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 "" | ||
} | ||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it is.