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 : MediaGo #3705

Merged
merged 13 commits into from
Jun 24, 2024
214 changes: 214 additions & 0 deletions adapters/mediago/mediago.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
package mediago

import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"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
}

type mediagoResponseBidExt struct {
MediaType string `json:"mediaType"`
}

// Builder builds a new instance of the MediaGo adapter for the given bidder with the given config.
func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
endpoint, 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: endpoint,
}
return bidder, nil
}

func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
var adapterRequests []*adapters.RequestData
var errs []error

adapterRequest, err := a.makeRequest(request)
if err == nil {
adapterRequests = append(adapterRequests, adapterRequest)
} else {
errs = append(errs, err)
}
return adapterRequests, errs
}

func (a *adapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, error) {

mediagoExt, err := getMediaGoExt(request)
if err != nil {
return nil, err
}
endPoint, err := a.getEndPoint(mediagoExt)
if err != nil {
return nil, err
}

preProcess(request)
reqBody, err := json.Marshal(request)
if err != nil {
return nil, err
}

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

return &adapters.RequestData{
Method: "POST",
Uri: endPoint,
Body: reqBody,
Headers: headers,
ImpIDs: openrtb_ext.GetImpIDs(request.Imp),
}, nil
}

// get MediaGoExt From ext.bidderparams or ext of First Imp. Only check and get first Imp.Ext.Bidder to ExtImpMediago
func getMediaGoExt(request *openrtb2.BidRequest) (*openrtb_ext.ExtMediaGo, error) {
SyntaxNode marked this conversation as resolved.
Show resolved Hide resolved
var extMediaGo openrtb_ext.ExtMediaGo
var extBidder adapters.ExtImpBidder

// first get the mediago ext from ext.bidderparams
reqExt := &openrtb_ext.ExtRequest{}
err := json.Unmarshal(request.Ext, &reqExt)
if err != nil {
err = json.Unmarshal(reqExt.Prebid.BidderParams, &extMediaGo)
if err != nil && extMediaGo.Token != "" {
return &extMediaGo, nil
}
}

// fallback to get token and region from first imp
imp := request.Imp[0]
err = json.Unmarshal(imp.Ext, &extBidder)
if err != nil {
return nil, err
}

var extImpMediaGo openrtb_ext.ExtImpMediaGo
err = json.Unmarshal(extBidder.Bidder, &extImpMediaGo)
if err != nil {
return nil, err
}
if extImpMediaGo.Token != "" {
extMediaGo.Token = extImpMediaGo.Token
extMediaGo.Region = extImpMediaGo.Region

return &extMediaGo, nil
Comment on lines +98 to +114
Copy link
Contributor

Choose a reason for hiding this comment

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

Based on this code block it seems like adapter code excepts all imps to have same bidder param values.

@SylviaF could you confirm if this is expected behaviour? If yes then this should be explicitly mentioned in bidder docs

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@onkarvhanumante Yes, this is expected behaviour. Which file do you mean by bidder doc?Can I submit the doc changes separately later? I'd like to get the code merged and released in new version in this week.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@onkarvhanumante I have change static/bidder-params/mediago.json.

Copy link
Contributor

Choose a reason for hiding this comment

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

@onkarvhanumante looks like @SylviaF made the docs change updates you requested on this pr.

@SylviaF can you also update the below file in our docs repo to reflect that you are adding a server adapter -> https://github.com/prebid/prebid.github.io/blob/master/dev-docs/bidders/mediago.md

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ChrisHuie I have request a PR to update the docs: prebid/prebid.github.io#5395

Copy link
Contributor

Choose a reason for hiding this comment

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

@onkarvhanumante looks like @SylviaF made the docs change updates you requested on this pr.

@SylviaF can you also update the below file in our docs repo to reflect that you are adding a server adapter -> https://github.com/prebid/prebid.github.io/blob/master/dev-docs/bidders/mediago.md

@SylviaF could you link docs PR in this PR description

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link

@JimmyLuoJW JimmyLuoJW Jun 11, 2024

Choose a reason for hiding this comment

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

Based on this code block it seems like adapter code excepts all imps to have same bidder param values.

@SylviaF could you confirm if this is expected behaviour? If yes then this should be explicitly mentioned in bidder docs

@onkarvhanumante I'm replying on behalf of @SylviaF : Yes, this is expected behavior. She has changed static/bidder-params/mediago.json

Copy link

@JimmyLuoJW JimmyLuoJW Jun 11, 2024

Choose a reason for hiding this comment

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

@onkarvhanumante looks like @SylviaF made the docs change updates you requested on this pr.
@SylviaF can you also update the below file in our docs repo to reflect that you are adding a server adapter -> https://github.com/prebid/prebid.github.io/blob/master/dev-docs/bidders/mediago.md

@SylviaF could you link docs PR in this PR description

@ChrisHuie replying on behalf of @SylviaF , she has requested a PR to update the docs: prebid/prebid.github.io#5395

}
return nil, errors.New("mediago token not found")

}

func getRegionInfo(region string) string {
switch region {
case "APAC":
return "jp"
onkarvhanumante marked this conversation as resolved.
Show resolved Hide resolved
case "EU":
return "eu"
case "US":
return "us"
default:
return "us"
}
}

func (a *adapter) getEndPoint(ext *openrtb_ext.ExtMediaGo) (string, error) {
endPointParams := macros.EndpointTemplateParams{
AccountID: url.PathEscape(ext.Token),
Host: url.PathEscape(getRegionInfo(ext.Region)),
}
return macros.ResolveMacros(a.EndpointTemplate, endPointParams)
}

func preProcess(request *openrtb2.BidRequest) {
for i := range request.Imp {
if request.Imp[i].Banner != nil {
banner := *request.Imp[i].Banner
if (banner.W == nil || banner.H == nil || *banner.W == 0 || *banner.H == 0) && len(banner.Format) > 0 {
firstFormat := banner.Format[0]
banner.W = &firstFormat.W
banner.H = &firstFormat.H
request.Imp[i].Banner = &banner
}
}
}
}

func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
if adapters.IsResponseStatusCodeNoContent(response) {
return nil, nil
}
if err := adapters.CheckResponseStatusCodeForErrors(response); err != nil {
return nil, []error{err}
}

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

bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)
var errs []error

for _, seatBid := range bidResp.SeatBid {
for idx := range seatBid.Bid {
mediaType, err := getBidType(seatBid.Bid[idx], internalRequest.Imp)
if err != nil {
errs = append(errs, err)
} else {
bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
Bid: &seatBid.Bid[idx],
BidType: mediaType,
})
}
}
}

return bidResponse, errs
}

func getBidType(bid openrtb2.Bid, imps []openrtb2.Imp) (openrtb_ext.BidType, error) {
switch bid.MType {
case openrtb2.MarkupBanner:
return openrtb_ext.BidTypeBanner, nil
case openrtb2.MarkupNative:
return openrtb_ext.BidTypeNative, nil
// case openrtb2.MarkupAudio:
// return openrtb_ext.BidTypeAudio, nil
// case openrtb2.MarkupVideo:
// return openrtb_ext.BidTypeVideo, nil
SyntaxNode marked this conversation as resolved.
Show resolved Hide resolved
default:
for _, imp := range imps {
if imp.ID == bid.ImpID {
if imp.Banner != nil {
return openrtb_ext.BidTypeBanner, nil
}
if imp.Native != nil {
return openrtb_ext.BidTypeNative, nil
}
}
}
Comment on lines +194 to +204
Copy link
Contributor

Choose a reason for hiding this comment

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

any particular reason to fallback to this logic when no case for MType matches?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@onkarvhanumante We retrieves the corresponding bid type through imp because our bidder api may has not yet fully returned mtype.
Since this is not an OpenRTB standard, we will implement special adaptation. However, to be safe, we would like to keep the fallback code.

Choose a reason for hiding this comment

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

any particular reason to fallback to this logic when no case for MType matches?

Hi @onkarvhanumante chiming in here for my colleague @SylviaF . She replied to this question last week but somehow the reply was not seen. Essentially she's saying the reason why we map the MType field according to the bid type in imp is that we follow the standard OpenRTB specs, and MType is not included in oRTB specs. We will make modifications based on Prebid's requirements, however just in case, we want to keep this fallback code to make sure we can bid properly.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@onkarvhanumante the Prebid Server committee discussed this thread the other day. We're ok with this fallback logic. Using the MType is preferable and there's no harm in having the fallback code. As I'm sure you're aware, a lot of other adapters only have the imp-based approach. I imagine you were just asking out of curiosity, but just in case, I want you to know that the committee is ok with this.

return "", &errortypes.BadServerResponse{
Message: fmt.Sprintf("Unsupported MType %d", bid.MType),
}
}

}
28 changes: 28 additions & 0 deletions adapters/mediago/mediago_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package mediago

import (
"testing"

"github.com/stretchr/testify/assert"

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

func TestJsonSamples(t *testing.T) {
bidder, buildErr := Builder(openrtb_ext.BidderMediaGo, config.Adapter{
Endpoint: "https://REGION.mediago.io/api/bid?tn={{.AccountID}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})

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

adapterstest.RunJSONBidderTest(t, "mediagotest", bidder)
}

func TestEndpointTemplateMalformed(t *testing.T) {
_, buildErr := Builder(openrtb_ext.BidderMediaGo, config.Adapter{Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})

assert.Error(t, buildErr)
}
114 changes: 114 additions & 0 deletions adapters/mediago/mediagotest/exemplary/sample-banner-apac.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
{
"mockBidRequest": {
"id": "test-request-id",
"imp": [
{
"id": "test-imp-id",
"banner": {
"format": [{"w": 320, "h": 50}]
},
"ext": {
"bidder": {
"token": "f9f2b1ef23fe2759c2cad0953029a94b",
"placementId": "testPlacementId",
"region": "APAC"
}
}
}
],
"site": {
"id": "test-site-id",
"page": "https://www.example.com/"
},
"ext": {
"prebid": {
"bidderparams": {
"bidder": {
"token": "f9f2b1ef23fe2759c2cad0953029a94b",
"region": "APAC"
}
}
}
}
},

"httpCalls": [
{
"expectedRequest": {
"uri": "https://REGION.mediago.io/api/bid?tn=f9f2b1ef23fe2759c2cad0953029a94b",
"body": {
"id": "test-request-id",
"imp": [
{
"id":"test-imp-id",
"banner": {
"format": [{"w": 320, "h": 50}],
"w": 320,
"h": 50
},
"ext": {
"bidder": {
"token": "f9f2b1ef23fe2759c2cad0953029a94b",
"region": "APAC",
"placementId": "testPlacementId"
}
}
}
],
"site": {
"id": "test-site-id",
"page": "https://www.example.com/"
},
"ext": {
"prebid": {
"bidderparams": {
"bidder": {
"token": "f9f2b1ef23fe2759c2cad0953029a94b",
"region": "APAC"
}
}
}
}
},
"impIDs":["test-imp-id"]
},
"mockResponse": {
"status": 200,
"body": {
"id": "test-request-id",
"seatbid": [
{
"seat": "mediago",
"bid": [{
"id": "test-imp-id",
"impid": "test-imp-id",
"price": 0.5,
"adm": "some-ads",
"crid": "crid_testid"
}]
}
],
"cur": "USD"
}
}
}
],

"expectedBidResponses": [
{
"currency": "USD",
"bids": [
{
"bid": {
"id": "test-imp-id",
"impid": "test-imp-id",
"price": 0.5,
"adm": "some-ads",
"crid": "crid_testid"
},
"type": "banner"
}
]
}
]
}
Loading
Loading