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: operaads #1916

Merged
merged 6 commits into from
Jul 21, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
233 changes: 233 additions & 0 deletions adapters/operaads/operaads.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
package operaads

import (
"encoding/json"
"fmt"
"github.com/prebid/prebid-server/macros"
"net/http"
"text/template"

"github.com/mxmCherry/openrtb/v15/openrtb2"
"github.com/prebid/prebid-server/adapters"
"github.com/prebid/prebid-server/config"
"github.com/prebid/prebid-server/errortypes"
"github.com/prebid/prebid-server/openrtb_ext"
)

type adapter struct {
epTemplate *template.Template
}

// Builder builds a new instance of the operaads adapter for the given bidder with the given config.
func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
epTemplate, err := template.New("endpoint").Parse(config.Endpoint)
if err != nil {
return nil, err
}
bidder := &adapter{
epTemplate: epTemplate,
}
return bidder, nil
}

func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
impCount := len(request.Imp)
requestData := make([]*adapters.RequestData, 0, impCount)
errs := []error{}
request, err := deepCopyRequest(request)
if err != nil {
errs = append(errs, &errortypes.BadInput{
Message: err.Error(),
})
return nil, errs
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

@yuanjize I see what you're trying to do here but I think this is a bit of overkill. I recommend removing the deep copy logic and instead considering my suggestion in convertImpression below.

headers := http.Header{}
headers.Add("Content-Type", "application/json;charset=utf-8")
headers.Add("Accept", "application/json")

err = checkRequest(request)
if err != nil {
errs = append(errs, err)
return nil, errs
}

for _, imp := range request.Imp {
requestCopy := *request
var bidderExt adapters.ExtImpBidder
if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
errs = append(errs, &errortypes.BadInput{
Message: err.Error(),
})
continue
}

var operaadsExt openrtb_ext.ImpExtOperaads
if err := json.Unmarshal(bidderExt.Bidder, &operaadsExt); err != nil {
errs = append(errs, &errortypes.BadInput{
Message: err.Error(),
})
continue
}
jizeyopera marked this conversation as resolved.
Show resolved Hide resolved

err := convertImpression(&imp)
if err != nil {
errs = append(errs, &errortypes.BadInput{
Message: err.Error(),
})
continue
}

imp.TagID = operaadsExt.PlacementID

requestCopy.Imp = []openrtb2.Imp{imp}
reqJSON, err := json.Marshal(&requestCopy)
if err != nil {
errs = append(errs, err)
return nil, errs
}

macro := macros.EndpointTemplateParams{PublisherID: operaadsExt.PublisherID, AccountID: operaadsExt.EndpointID}
endpoint, err := macros.ResolveMacros(*a.epTemplate, &macro)
if err != nil {
errs = append(errs, err)
continue
}
reqData := &adapters.RequestData{
Method: http.MethodPost,
Uri: endpoint,
Body: reqJSON,
Headers: headers,
}
requestData = append(requestData, reqData)
}
return requestData, errs
}

func checkRequest(request *openrtb2.BidRequest) error {
if request.Device == nil || len(request.Device.OS) == 0 {
return &errortypes.BadInput{
Message: "Impression is missing device OS information",
}
}

return nil
}

func convertImpression(imp *openrtb2.Imp) error {
if imp.Banner != nil {
bannerCopy, err := convertBanner(imp.Banner)
if err != nil {
return err
}
imp.Banner = bannerCopy
}
if imp.Native != nil && imp.Native.Request != "" {
v := make(map[string]interface{})
err := json.Unmarshal([]byte(imp.Native.Request), &v)
if err != nil {
return err
}
_, ok := v["native"]
if !ok {
body, err := json.Marshal(struct {
Native interface{} `json:"native"`
}{
Native: v,
})
Comment on lines +124 to +129
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you please add a test case for when imp.Native.Request contains valid JSON but is missing the native key?

if err != nil {
return err
}
imp.Native.Request = string(body)
Copy link
Collaborator

Choose a reason for hiding this comment

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

The race detected by the validate-merge job is most likely occurring because Native is shared memory. You'll need to make a copy of it before modifying it.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think you can solve the race issue by simply replacing imp.Native.Request = string(body) with the following:

nativeCopy := *imp.Native
nativeCopy.Request = string(body)
imp.Native = &nativeCopy

Copy link

@yuanjize yuanjize Jul 20, 2021

Choose a reason for hiding this comment

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

Is there any other thread that will use imp.Native when I write it?

Copy link
Collaborator

Choose a reason for hiding this comment

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

There shouldn't be. Your adapter is working with shallow copies of imps. Since Native is a pointer and we don't deep copy imps before calling the adapters, you need to copy the Native object that is being shared by your adapter and core PBS before modifying it so only your imp copy points to the modified version. Does that help?

Choose a reason for hiding this comment

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

yes,thanks.

}
}
return nil
}

// make sure that banner has openrtb 2.3-compatible size information
func convertBanner(banner *openrtb2.Banner) (*openrtb2.Banner, error) {
if banner.W == nil || banner.H == nil || *banner.W == 0 || *banner.H == 0 {
if len(banner.Format) > 0 {
f := banner.Format[0]

bannerCopy := *banner

bannerCopy.W = openrtb2.Int64Ptr(f.W)
bannerCopy.H = openrtb2.Int64Ptr(f.H)

return &bannerCopy, nil
} else {
return nil, &errortypes.BadInput{
Message: "Size information missing for banner",
}
}
Comment on lines +153 to +157
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please add test coverage for this else block.

}
return banner, nil
}

func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
if response.StatusCode == http.StatusNoContent {
return nil, nil
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you please add test coverage here for when the status code is StatusNoContent?

}

if response.StatusCode == http.StatusBadRequest {
return nil, []error{&errortypes.BadInput{
Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
}}
}
Comment on lines +168 to +171
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please add test coverage for StatusBadRequest.


if response.StatusCode != http.StatusOK {
return nil, []error{&errortypes.BadServerResponse{
Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
}}
}
Comment on lines +174 to +177
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please add test coverage here as well.


var parsedResponse openrtb2.BidResponse
if err := json.Unmarshal(response.Body, &parsedResponse); err != nil {
return nil, []error{&errortypes.BadServerResponse{
Message: err.Error(),
}}
}

bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)

for _, sb := range parsedResponse.SeatBid {
for i := 0; i < len(sb.Bid); i++ {
bid := sb.Bid[i]
if bid.Price != 0 {
bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
Bid: &bid,
BidType: getMediaTypeForImp(bid.ImpID, internalRequest.Imp),
})
}
}
}
return bidResponse, nil
}

func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType {
mediaType := openrtb_ext.BidTypeBanner
for _, imp := range imps {
if imp.ID == impId {
if imp.Video != nil {
mediaType = openrtb_ext.BidTypeVideo
} else if imp.Native != nil {
mediaType = openrtb_ext.BidTypeNative
}
return mediaType
}
}
return mediaType
}

func deepCopyRequest(request *openrtb2.BidRequest) (*openrtb2.BidRequest, error) {
reqJson, err := json.Marshal(request)
if err != nil {
return nil, err
}
var reqCopy openrtb2.BidRequest
err = json.Unmarshal(reqJson, &reqCopy)
if err != nil {
return nil, err
}
return &reqCopy, err
}
20 changes: 20 additions & 0 deletions adapters/operaads/operaads_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package operaads

import (
"testing"

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

func TestJsonSamples(t *testing.T) {
bidder, buildErr := Builder(openrtb_ext.BidderOperaads, config.Adapter{
Endpoint: "http://example.com/operaads/ortb/v2/{{.PublisherID}}?ep={{.AccountID}}"})

if buildErr != nil {
t.Fatalf("Builder returned unexpected error %v", buildErr)
}

adapterstest.RunJSONBidderTest(t, "operaadstest", bidder)
}
137 changes: 137 additions & 0 deletions adapters/operaads/operaadstest/exemplary/native.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
{
"mockBidRequest":{
"id":"some-req-id",
"imp":[
{
"id":"some-imp-id",
"native":{
"request":"{\"native\":{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}}",
"ver":"1.1"
},
"ext":{
"bidder":{
"placementId":"s123456",
"endpointId":"ep19978",
"publisherId":"pub123"
}
}
}
],
"site":{
"domain":"example.com",
"page":"example.com"
},
"device":{
"ip":"152.193.6.74",
"os":"android"
},
"user":{
"id":"db089de9-a62e-4861-a881-0ff15e052516",
"buyeruid":"8299345306627569435"
},
"tmax":500
},
"httpcalls":[
{
"expectedRequest":{
"uri":"http://example.com/operaads/ortb/v2/pub123?ep=ep19978",
"body":{
"id":"some-req-id",
"imp":[
{
"id":"some-imp-id",
"tagid":"s123456",
"ext":{
"bidder":{
"placementId":"s123456",
"endpointId":"ep19978",
"publisherId":"pub123"
}
},
"native":{
"request":"{\"native\":{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}}",
"ver":"1.1"
}
}
],
"site":{
"domain":"example.com",
"page":"example.com"
},
"device":{
"ip":"152.193.6.74",
"os":"android"
},
"user":{
"id":"db089de9-a62e-4861-a881-0ff15e052516",
"buyeruid":"8299345306627569435"
},
"tmax":500
}
},
"mockResponse":{
"status":200,
"body":{
"id":"some-req-id",
"seatbid":[
{
"bid":[
{
"id":"928185755156387460",
"impid":"some-imp-id",
"price":1,
"adid":"69595837",
"adm":"{\"assets\":[{\"id\": 2,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 344,\"h\": 194}},{\"id\": 3,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 100,\"h\": 100}} ,{\"id\": 1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"id\": 4,\"data\":{\"value\":\"This is a Prebid Native Creative. There are many like it, but this one is mine.\"}},{\"id\": 6,\"data\":{\"value\":\"Please.\"}} ],\"link\":{\"url\":\"http://example.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=${AUCTION_PRICE}/cnd=%21OwwGAQiGmooHEL3llyEY-PxDIAQoADoRZGVmYXVsdCNOWU0yOjQwMjM./bn=75922/test=1/referrer=prebid.org/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html\"},\"imptrackers\":[\"http://example.com/openrtb_win?e=wqT_3QLFBqBFAwAAAwDWAAUBCNmku9QFEIi6jeuTm_LoTRib7t2u2tLMlnMqNgkAAAECCPA_EQEHEAAA8D8ZCQkIAAAhCQkI8D8pEQkAMQkJqAAAMKqI2wQ4vgdAvgdIAlC95ZchWPj8Q2AAaJFAeJLRBIABAYoBA1VTRJIFBvBQmAEBoAEBqAEBsAEAuAECwAEEyAEC0AEJ2AEA4AEB8AEAigI7dWYoJ2EnLCAxMzc2ODYwLCAxNTE5MzA5NDAxKTt1ZigncicsIDY5NTk1ODM3Nh4A8IqSAvUBIXRETkdfUWlHbW9vSEVMM2xseUVZQUNENF9FTXdBRGdBUUFSSXZnZFFxb2piQkZnQVlMTURhQUJ3QUhnQWdBRUFpQUVBa0FFQm1BRUJvQUVCcUFFRHNBRUF1UUVwaTRpREFBRHdQOEVCS1l1SWd3QUE4RF9KQVhfelYzek1zXzBfMlFFQUFBAQMkRHdQLUFCQVBVQgEOLEFKZ0NBS0FDQUxVQwUQBEwwCQjwTE1BQ0FNZ0NBT0FDQU9nQ0FQZ0NBSUFEQVpBREFKZ0RBYWdEaHBxS0I3b0RFV1JsWm1GMWJIUWpUbGxOTWpvME1ESXqaAjkhT3d3R0FRNvgA8E4tUHhESUFRb0FEb1JaR1ZtWVhWc2RDTk9XVTB5T2pRd01qTS7YAugH4ALH0wHqAgpwcmViaWQub3Jn8gIRCgZBRFZfSUQSBzEzNzY4NjDyARQMQ1BHXwEUNDM1MDMwOTjyAhEKBUNQARPwmQgxNDg0NzIzOIADAYgDAZADAJgDFKADAaoDAMADkBzIAwDYAwDgAwDoAwD4AwOABACSBAkvb3BlbnJ0YjKYBACiBAwxNTIuMTkzLjYuNzSoBJrMI7IEDAgAEAAYACAAMAA4ALgEAMAEAMgEANIEEWRlZmF1bHQjTllNMjo0MDIz2gQCCADgBADwBL3llyGIBQGYBQCgBf____8FA1ABqgULc29tZS1yZXEtaWTABQDJBQAFARTwP9IFCQkFC2QAAADYBQHgBQHwBd4C-gUECAAQAJAGAZgGAA..&s=08b1535744639c904684afe46e3c6c0e4786089f&test=1&referrer=prebid.org&pp=${AUCTION_PRICE}\"]}",
"adomain":[
"example.com"
],
"iurl":"http://example.com/cr?id=69595837",
"cid":"958",
"crid":"69595837",
"cat":[
"IAB3-1"
],
"ext":{

}
}
],
"seat":"958"
}
],
"bidid":"8141327771600527856",
"cur":"USD"
}
}
}
],
"expectedBidResponses":[
{
"currency":"USD",
"bids":[
{
"bid":{
"id":"928185755156387460",
"impid":"some-imp-id",
"price":1,
"adm":"{\"assets\":[{\"id\": 2,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 344,\"h\": 194}},{\"id\": 3,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 100,\"h\": 100}} ,{\"id\": 1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"id\": 4,\"data\":{\"value\":\"This is a Prebid Native Creative. There are many like it, but this one is mine.\"}},{\"id\": 6,\"data\":{\"value\":\"Please.\"}} ],\"link\":{\"url\":\"http://example.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=${AUCTION_PRICE}/cnd=%21OwwGAQiGmooHEL3llyEY-PxDIAQoADoRZGVmYXVsdCNOWU0yOjQwMjM./bn=75922/test=1/referrer=prebid.org/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html\"},\"imptrackers\":[\"http://example.com/openrtb_win?e=wqT_3QLFBqBFAwAAAwDWAAUBCNmku9QFEIi6jeuTm_LoTRib7t2u2tLMlnMqNgkAAAECCPA_EQEHEAAA8D8ZCQkIAAAhCQkI8D8pEQkAMQkJqAAAMKqI2wQ4vgdAvgdIAlC95ZchWPj8Q2AAaJFAeJLRBIABAYoBA1VTRJIFBvBQmAEBoAEBqAEBsAEAuAECwAEEyAEC0AEJ2AEA4AEB8AEAigI7dWYoJ2EnLCAxMzc2ODYwLCAxNTE5MzA5NDAxKTt1ZigncicsIDY5NTk1ODM3Nh4A8IqSAvUBIXRETkdfUWlHbW9vSEVMM2xseUVZQUNENF9FTXdBRGdBUUFSSXZnZFFxb2piQkZnQVlMTURhQUJ3QUhnQWdBRUFpQUVBa0FFQm1BRUJvQUVCcUFFRHNBRUF1UUVwaTRpREFBRHdQOEVCS1l1SWd3QUE4RF9KQVhfelYzek1zXzBfMlFFQUFBAQMkRHdQLUFCQVBVQgEOLEFKZ0NBS0FDQUxVQwUQBEwwCQjwTE1BQ0FNZ0NBT0FDQU9nQ0FQZ0NBSUFEQVpBREFKZ0RBYWdEaHBxS0I3b0RFV1JsWm1GMWJIUWpUbGxOTWpvME1ESXqaAjkhT3d3R0FRNvgA8E4tUHhESUFRb0FEb1JaR1ZtWVhWc2RDTk9XVTB5T2pRd01qTS7YAugH4ALH0wHqAgpwcmViaWQub3Jn8gIRCgZBRFZfSUQSBzEzNzY4NjDyARQMQ1BHXwEUNDM1MDMwOTjyAhEKBUNQARPwmQgxNDg0NzIzOIADAYgDAZADAJgDFKADAaoDAMADkBzIAwDYAwDgAwDoAwD4AwOABACSBAkvb3BlbnJ0YjKYBACiBAwxNTIuMTkzLjYuNzSoBJrMI7IEDAgAEAAYACAAMAA4ALgEAMAEAMgEANIEEWRlZmF1bHQjTllNMjo0MDIz2gQCCADgBADwBL3llyGIBQGYBQCgBf____8FA1ABqgULc29tZS1yZXEtaWTABQDJBQAFARTwP9IFCQkFC2QAAADYBQHgBQHwBd4C-gUECAAQAJAGAZgGAA..&s=08b1535744639c904684afe46e3c6c0e4786089f&test=1&referrer=prebid.org&pp=${AUCTION_PRICE}\"]}",
"adid":"69595837",
"adomain":[
"example.com"
],
"iurl":"http://example.com/cr?id=69595837",
"cid":"958",
"crid":"69595837",
"cat":[
"IAB3-1"
],
"ext":{

}
},
"type":"native"
}
]
}
]
}
Loading