-
Notifications
You must be signed in to change notification settings - Fork 739
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: AdTonos #3853
New Adapter: AdTonos #3853
Changes from all commits
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,142 @@ | ||
package adtonos | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
"text/template" | ||
|
||
"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/macros" | ||
"github.com/prebid/prebid-server/v2/openrtb_ext" | ||
) | ||
|
||
type adapter struct { | ||
endpointTemplate *template.Template | ||
} | ||
|
||
func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { | ||
template, err := template.New("endpointTemplate").Parse(config.Endpoint) | ||
if err != nil { | ||
return nil, fmt.Errorf("unable to parse endpoint url template: %v", err) | ||
} | ||
|
||
bidder := &adapter{ | ||
endpointTemplate: template, | ||
} | ||
return bidder, nil | ||
} | ||
|
||
func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { | ||
var bidderExt adapters.ExtImpBidder | ||
if err := json.Unmarshal(request.Imp[0].Ext, &bidderExt); err != nil { | ||
return nil, []error{&errortypes.BadInput{ | ||
Message: fmt.Sprintf("Invalid imp.ext for impression index %d. Error Infomation: %s", 0, err.Error()), | ||
}} | ||
} | ||
var impExt openrtb_ext.ImpExtAdTonos | ||
if err := json.Unmarshal(bidderExt.Bidder, &impExt); err != nil { | ||
return nil, []error{&errortypes.BadInput{ | ||
Message: fmt.Sprintf("Invalid imp.ext.bidder for impression index %d. Error Infomation: %s", 0, err.Error()), | ||
}} | ||
} | ||
|
||
endpoint, err := a.buildEndpointURL(&impExt) | ||
if err != nil { | ||
return nil, []error{err} | ||
} | ||
|
||
requestJson, err := json.Marshal(request) | ||
if err != nil { | ||
return nil, []error{err} | ||
} | ||
|
||
headers := http.Header{} | ||
headers.Add("Content-Type", "application/json;charset=utf-8") | ||
headers.Add("Accept", "application/json") | ||
|
||
requestData := &adapters.RequestData{ | ||
Method: "POST", | ||
Uri: endpoint, | ||
Body: requestJson, | ||
Headers: headers, | ||
ImpIDs: openrtb_ext.GetImpIDs(request.Imp), | ||
} | ||
|
||
return []*adapters.RequestData{requestData}, nil | ||
} | ||
|
||
func (a *adapter) buildEndpointURL(params *openrtb_ext.ImpExtAdTonos) (string, error) { | ||
endpointParams := macros.EndpointTemplateParams{PublisherID: params.SupplierID} | ||
return macros.ResolveMacros(a.endpointTemplate, endpointParams) | ||
} | ||
|
||
func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { | ||
if adapters.IsResponseStatusCodeNoContent(responseData) { | ||
return nil, nil | ||
} | ||
|
||
if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil { | ||
return nil, []error{err} | ||
} | ||
|
||
var response openrtb2.BidResponse | ||
if err := json.Unmarshal(responseData.Body, &response); err != nil { | ||
return nil, []error{err} | ||
} | ||
|
||
bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) | ||
bidResponse.Currency = response.Cur | ||
var errors []error | ||
for _, seatBid := range response.SeatBid { | ||
for i := range seatBid.Bid { | ||
bidType, err := getMediaTypeForBid(seatBid.Bid[i], request.Imp) | ||
if err != nil { | ||
errors = append(errors, err) | ||
continue | ||
} | ||
b := &adapters.TypedBid{ | ||
Bid: &seatBid.Bid[i], | ||
BidType: bidType, | ||
} | ||
bidResponse.Bids = append(bidResponse.Bids, b) | ||
} | ||
} | ||
return bidResponse, errors | ||
} | ||
|
||
func getMediaTypeForBid(bid openrtb2.Bid, requestImps []openrtb2.Imp) (openrtb_ext.BidType, error) { | ||
if bid.MType != 0 { | ||
// If present, use explicit markup type annotation from the bidder: | ||
switch bid.MType { | ||
case openrtb2.MarkupAudio: | ||
return openrtb_ext.BidTypeAudio, nil | ||
case openrtb2.MarkupVideo: | ||
return openrtb_ext.BidTypeVideo, nil | ||
case openrtb2.MarkupBanner: | ||
return openrtb_ext.BidTypeBanner, nil | ||
case openrtb2.MarkupNative: | ||
return openrtb_ext.BidTypeNative, nil | ||
} | ||
} | ||
// As a fallback, guess markup type based on requested type - AdTonos is an audio company so we prioritize that. | ||
for _, requestImp := range requestImps { | ||
if requestImp.ID == bid.ImpID { | ||
if requestImp.Audio != nil { | ||
return openrtb_ext.BidTypeAudio, nil | ||
} else if requestImp.Video != nil { | ||
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. Consider this as a suggestion. The current implementation follows an anti-pattern, assumes that if there is a multi-format request, the media type defaults to openrtb_ext.BidTypeVideo, nil. Prebid server expects the media type to be explicitly set in the adapter response. Therefore, we strongly recommend implementing a pattern where the adapter server sets the MType field in the response to accurately determine the media type for the impression. 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 think the review bot may be wrong here - we default to audio, not video. 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. |
||
return openrtb_ext.BidTypeVideo, nil | ||
} else { | ||
return "", &errortypes.BadInput{ | ||
Message: fmt.Sprintf("Unsupported bidtype for bid: \"%s\"", bid.ImpID), | ||
} | ||
} | ||
} | ||
} | ||
return "", &errortypes.BadInput{ | ||
Message: fmt.Sprintf("Failed to find impression: \"%s\"", bid.ImpID), | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package adtonos | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/prebid/prebid-server/v2/adapters/adapterstest" | ||
"github.com/prebid/prebid-server/v2/config" | ||
"github.com/prebid/prebid-server/v2/openrtb_ext" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestJsonSamples(t *testing.T) { | ||
bidder, buildErr := Builder(openrtb_ext.BidderAdTonos, config.Adapter{ | ||
Endpoint: "http://exchange.example.com/bid/{{.PublisherID}}"}, | ||
config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) | ||
|
||
if buildErr != nil { | ||
t.Fatalf("Builder returned unexpected error %v", buildErr) | ||
} | ||
|
||
adapterstest.RunJSONBidderTest(t, "adtonostest", bidder) | ||
} | ||
|
||
func TestEndpointTemplateMalformed(t *testing.T) { | ||
_, buildErr := Builder(openrtb_ext.BidderAdTonos, config.Adapter{ | ||
Endpoint: "{{Malformed}}"}, | ||
config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) | ||
|
||
assert.Error(t, buildErr) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
{ | ||
"mockBidRequest": { | ||
"id": "some-request-id", | ||
"tmax": 1000, | ||
"imp": [ | ||
{ | ||
"id": "some-impression-id", | ||
"bidfloor": 4.2, | ||
"ext": { | ||
"bidder": { | ||
"supplierId": "777XYZ123" | ||
} | ||
}, | ||
"audio": { | ||
"mimes": [ | ||
"audio/mpeg" | ||
] | ||
} | ||
} | ||
], | ||
"test": 1, | ||
"site": { | ||
"publisher": { | ||
"id": "1" | ||
}, | ||
"page": "http://www.example.com", | ||
"domain": "www.example.com" | ||
}, | ||
"device": {} | ||
}, | ||
"httpCalls": [ | ||
{ | ||
"expectedRequest": { | ||
"uri": "http://exchange.example.com/bid/777XYZ123", | ||
"headers": { | ||
"Content-Type": [ | ||
"application/json;charset=utf-8" | ||
], | ||
"Accept": [ | ||
"application/json" | ||
] | ||
}, | ||
"body": { | ||
"id": "some-request-id", | ||
"tmax": 1000, | ||
"imp": [ | ||
{ | ||
"id": "some-impression-id", | ||
"bidfloor": 4.2, | ||
"ext": { | ||
"bidder": { | ||
"supplierId": "777XYZ123" | ||
} | ||
}, | ||
"audio": { | ||
"mimes": [ | ||
"audio/mpeg" | ||
] | ||
} | ||
} | ||
], | ||
"site": { | ||
"publisher": { | ||
"id": "1" | ||
}, | ||
"page": "http://www.example.com", | ||
"domain": "www.example.com" | ||
}, | ||
"device": {}, | ||
"test": 1 | ||
}, | ||
"impIDs":["some-impression-id"] | ||
}, | ||
"mockResponse": { | ||
"status": 200, | ||
"body": { | ||
"id": "some-request-id", | ||
"cur": "USD", | ||
"seatbid": [ | ||
{ | ||
"bid": [ | ||
{ | ||
"id": "1", | ||
"impid": "some-impression-id", | ||
"crid": "some-creative-id", | ||
"adm": "<VAST>TAG</VAST>", | ||
"price": 6.5, | ||
"mtype": 3 | ||
} | ||
] | ||
} | ||
] | ||
} | ||
} | ||
} | ||
], | ||
"expectedBidResponses": [ | ||
{ | ||
"currency": "USD", | ||
"bids": [ | ||
{ | ||
"bid": { | ||
"id": "1", | ||
"impid": "some-impression-id", | ||
"crid": "some-creative-id", | ||
"adm": "<VAST>TAG</VAST>", | ||
"price": 6.5, | ||
"mtype": 3 | ||
}, | ||
"type": "audio" | ||
} | ||
] | ||
} | ||
] | ||
} |
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.
Consider this as a suggestion. The current implementation follows an anti-pattern, assumes that if there is a multi-format request, the media type defaults to openrtb_ext.BidTypeAudio, nil. Prebid server expects the media type to be explicitly set in the adapter response. Therefore, we strongly recommend implementing a pattern where the adapter server sets the MType field in the response to accurately determine the media type for the impression.
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.
(Leaving this here because AdTonos is an audio ad exchange - we incidentally do audio-as-video for publishers which absolutely cannot play an audio ad due to technical constraints, but really we just want to return VASTs with sound only.)
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.
@rkaw92 what bot is suggesting is to use Mtype field in the response to decide returned mediaType for an impression instead of relying on impID. For example,
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.
Okay, makes sense. We don't have a way of always returning mtype currently, but I can add logic that will optionally use it when present, and fall back to the current heuristic if not. In the future, when mtype coverage increases, the code will have anticipated it.
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've added handling for when mtype is explicitly indicated and left the guesstimation in place as a fallback, does that look better?