From 0362eb44e417f0cf1d9387a3795bba671a579068 Mon Sep 17 00:00:00 2001 From: dengxinjing <43231655+dengxinjing@users.noreply.github.com> Date: Thu, 7 Mar 2024 17:22:42 +0800 Subject: [PATCH] New Adapter: Roulax (#3447) Co-authored-by: dengxinjing --- adapters/roulax/roulax.go | 115 +++++++++++++++++ adapters/roulax/roulax_test.go | 24 ++++ .../roulaxtest/exemplary/simple-banner.json | 115 +++++++++++++++++ .../roulaxtest/exemplary/simple-native.json | 97 ++++++++++++++ .../roulaxtest/exemplary/simple-video.json | 121 ++++++++++++++++++ .../supplemental/no-bid-response.json | 79 ++++++++++++ exchange/adapter_builders.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_roulax.go | 6 + static/bidder-info/roulax.yaml | 14 ++ static/bidder-params/roulax.json | 19 +++ 11 files changed, 594 insertions(+) create mode 100644 adapters/roulax/roulax.go create mode 100644 adapters/roulax/roulax_test.go create mode 100644 adapters/roulax/roulaxtest/exemplary/simple-banner.json create mode 100644 adapters/roulax/roulaxtest/exemplary/simple-native.json create mode 100644 adapters/roulax/roulaxtest/exemplary/simple-video.json create mode 100644 adapters/roulax/roulaxtest/supplemental/no-bid-response.json create mode 100644 openrtb_ext/imp_roulax.go create mode 100644 static/bidder-info/roulax.yaml create mode 100644 static/bidder-params/roulax.json diff --git a/adapters/roulax/roulax.go b/adapters/roulax/roulax.go new file mode 100644 index 00000000000..ea9e1e73cf2 --- /dev/null +++ b/adapters/roulax/roulax.go @@ -0,0 +1,115 @@ +package roulax + +import ( + "encoding/json" + "fmt" + "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/macros" + "github.com/prebid/prebid-server/v2/openrtb_ext" + "net/http" + "text/template" +) + +type adapter struct { + endpoint *template.Template +} + +// Builder builds a new instance of the Roulax adapter for the given bidder with the given config. +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{ + endpoint: template, + } + return bidder, nil +} + +// getImpAdotExt parses and return first imp ext or nil +func getImpRoulaxExt(imp *openrtb2.Imp) (openrtb_ext.ExtImpRoulax, error) { + var extBidder adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &extBidder); err != nil { + return openrtb_ext.ExtImpRoulax{}, err + } + var extImpRoulax openrtb_ext.ExtImpRoulax + if err := json.Unmarshal(extBidder.Bidder, &extImpRoulax); err != nil { + return openrtb_ext.ExtImpRoulax{}, err + } + return extImpRoulax, nil +} + +// MakeRequests makes the HTTP requests which should be made to fetch bids. +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) (res []*adapters.RequestData, errs []error) { + reqJson, err := json.Marshal(request) + if err != nil { + return nil, append(errs, err) + } + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + roulaxExt, err := getImpRoulaxExt(&request.Imp[0]) + if err != nil { + return nil, append(errs, err) + } + + endpointParams := macros.EndpointTemplateParams{AccountID: roulaxExt.Pid, PublisherID: roulaxExt.PublisherPath} + url, err := macros.ResolveMacros(a.endpoint, endpointParams) + if err != nil { + return nil, append(errs, err) + } + + return []*adapters.RequestData{{ + Method: "POST", + Uri: url, + Body: reqJson, + Headers: headers, + }}, errs +} + +// MakeBids unpacks the server's response into Bids. +func (a *adapter) MakeBids(request *openrtb2.BidRequest, _ *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 errs []error + for _, seatBid := range response.SeatBid { + for i, bid := range seatBid.Bid { + bidType, err := getMediaTypeForBid(bid) + if err != nil { + errs = append(errs, err) + continue + } + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: bidType, + }) + } + } + return bidResponse, errs +} + +func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + case openrtb2.MarkupNative: + return openrtb_ext.BidTypeNative, nil + default: + return "", fmt.Errorf("Unable to fetch mediaType in impID: %s, mType: %d", bid.ImpID, bid.MType) + } +} diff --git a/adapters/roulax/roulax_test.go b/adapters/roulax/roulax_test.go new file mode 100644 index 00000000000..0b13b9874c4 --- /dev/null +++ b/adapters/roulax/roulax_test.go @@ -0,0 +1,24 @@ +package roulax + +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" +) + +const testsDir = "roulaxtest" +const testsBidderEndpoint = "http://dsp.rcoreads.com/api/vidmate?pid=vidmate_android_banner" + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder( + openrtb_ext.BidderRoulax, + config.Adapter{Endpoint: testsBidderEndpoint}, + config.Server{ExternalUrl: "http://dsp.rcoreads.com/api/vidmate?pid=vidmate_android_banner", GvlID: 1, DataCenter: "2"}) + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, testsDir, bidder) +} diff --git a/adapters/roulax/roulaxtest/exemplary/simple-banner.json b/adapters/roulax/roulaxtest/exemplary/simple-banner.json new file mode 100644 index 00000000000..1f63e607fbf --- /dev/null +++ b/adapters/roulax/roulaxtest/exemplary/simple-banner.json @@ -0,0 +1,115 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ] + }, + "ext": { + "bidder": { + "PublisherPath": "72721", + "Pid": "mvo", + "zone": "1r" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://dsp.rcoreads.com/api/vidmate?pid=vidmate_android_banner", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ] + }, + "ext": { + "bidder": { + "PublisherPath": "72721", + "Pid": "mvo", + "zone": "1r" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "yahoo.com" + ], + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "mtype": 1 + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": [ + "yahoo.com" + ], + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 250, + "mtype": 1 + }, + "type": "banner" + }] + } + ] +} diff --git a/adapters/roulax/roulaxtest/exemplary/simple-native.json b/adapters/roulax/roulaxtest/exemplary/simple-native.json new file mode 100644 index 00000000000..4c0170c37ae --- /dev/null +++ b/adapters/roulax/roulaxtest/exemplary/simple-native.json @@ -0,0 +1,97 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "test-native-request" + }, + "ext": { + "bidder": { + "PublisherPath": "72721", + "Pid": "mvo", + "zone": "1r" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://dsp.rcoreads.com/api/vidmate?pid=vidmate_android_banner", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "test-native-request" + }, + "ext": { + "bidder": { + "PublisherPath": "72721", + "Pid": "mvo", + "zone": "1r" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "yahoo.com" + ], + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "mtype": 4 + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": [ + "yahoo.com" + ], + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 250, + "mtype": 4 + }, + "type": "native" + }] + } + ] +} \ No newline at end of file diff --git a/adapters/roulax/roulaxtest/exemplary/simple-video.json b/adapters/roulax/roulaxtest/exemplary/simple-video.json new file mode 100644 index 00000000000..8b3e30dfb21 --- /dev/null +++ b/adapters/roulax/roulaxtest/exemplary/simple-video.json @@ -0,0 +1,121 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 1, + "maxduration": 2, + "protocols": [1,3,5], + "w": 1020, + "h": 780, + "startdelay": 1, + "placement": 1, + "playbackmethod": [2], + "delivery": [1], + "api": [1,2,3,4] + }, + "ext": { + "bidder": { + "PublisherPath": "72721", + "Pid": "mvo", + "zone": "1r" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://dsp.rcoreads.com/api/vidmate?pid=vidmate_android_banner", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 1, + "maxduration": 2, + "protocols": [1,3,5], + "w": 1020, + "h": 780, + "startdelay": 1, + "placement": 1, + "playbackmethod": [2], + "delivery": [1], + "api": [1,2,3,4] + }, + "ext": { + "bidder": { + "PublisherPath": "72721", + "Pid": "mvo", + "zone": "1r" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "yahoo.com" + ], + "cid": "958", + "crid": "29681110", + "h": 250, + "w": 300, + "mtype": 2 + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [{ + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "29681110", + "adomain": [ + "yahoo.com" + ], + "cid": "958", + "crid": "29681110", + "w": 300, + "h": 250, + "mtype": 2 + }, + "type": "video" + }] + } + ] +} \ No newline at end of file diff --git a/adapters/roulax/roulaxtest/supplemental/no-bid-response.json b/adapters/roulax/roulaxtest/supplemental/no-bid-response.json new file mode 100644 index 00000000000..7d9ff2e6ebb --- /dev/null +++ b/adapters/roulax/roulaxtest/supplemental/no-bid-response.json @@ -0,0 +1,79 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 1, + "maxduration": 2, + "protocols": [1,3,5], + "w": 1020, + "h": 780, + "startdelay": 1, + "placement": 1, + "playbackmethod": [2], + "delivery": [1], + "api": [1,2,3,4] + }, + "ext": { + "bidder": { + "PublisherPath": "72721", + "Pid": "mvo", + "zone": "1r" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://dsp.rcoreads.com/api/vidmate?pid=vidmate_android_banner", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 1, + "maxduration": 2, + "protocols": [1,3,5], + "w": 1020, + "h": 780, + "startdelay": 1, + "placement": 1, + "playbackmethod": [2], + "delivery": [1], + "api": [1,2,3,4] + }, + "ext": { + "bidder": { + "PublisherPath": "72721", + "Pid": "mvo", + "zone": "1r" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [] + } + ] +} \ No newline at end of file diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 90a0059ca9f..57318c006da 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -147,6 +147,7 @@ import ( "github.com/prebid/prebid-server/v2/adapters/revcontent" "github.com/prebid/prebid-server/v2/adapters/richaudience" "github.com/prebid/prebid-server/v2/adapters/rise" + "github.com/prebid/prebid-server/v2/adapters/roulax" "github.com/prebid/prebid-server/v2/adapters/rtbhouse" "github.com/prebid/prebid-server/v2/adapters/rubicon" salunamedia "github.com/prebid/prebid-server/v2/adapters/sa_lunamedia" @@ -352,6 +353,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderRevcontent: revcontent.Builder, openrtb_ext.BidderRichaudience: richaudience.Builder, openrtb_ext.BidderRise: rise.Builder, + openrtb_ext.BidderRoulax: roulax.Builder, openrtb_ext.BidderRTBHouse: rtbhouse.Builder, openrtb_ext.BidderRubicon: rubicon.Builder, openrtb_ext.BidderSeedingAlliance: seedingAlliance.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index f1493add953..baf54f97a7f 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -165,6 +165,7 @@ var coreBidderNames []BidderName = []BidderName{ BidderRevcontent, BidderRichaudience, BidderRise, + BidderRoulax, BidderRTBHouse, BidderRubicon, BidderSeedingAlliance, @@ -446,6 +447,7 @@ const ( BidderRevcontent BidderName = "revcontent" BidderRichaudience BidderName = "richaudience" BidderRise BidderName = "rise" + BidderRoulax BidderName = "roulax" BidderRTBHouse BidderName = "rtbhouse" BidderRubicon BidderName = "rubicon" BidderSeedingAlliance BidderName = "seedingAlliance" diff --git a/openrtb_ext/imp_roulax.go b/openrtb_ext/imp_roulax.go new file mode 100644 index 00000000000..8ce720f42ba --- /dev/null +++ b/openrtb_ext/imp_roulax.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ExtImpRoulax struct { + Pid string `json:"Pid,omitempty"` + PublisherPath string `json:"publisherPath,omitempty"` +} diff --git a/static/bidder-info/roulax.yaml b/static/bidder-info/roulax.yaml new file mode 100644 index 00000000000..646a9cbc77a --- /dev/null +++ b/static/bidder-info/roulax.yaml @@ -0,0 +1,14 @@ +endpoint: "http://dsp.rcoreads.com/api/{{.PublisherID}}?pid={{.AccountID}}" +maintainer: + email: "bussiness@roulax.io" +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native diff --git a/static/bidder-params/roulax.json b/static/bidder-params/roulax.json new file mode 100644 index 00000000000..adcf847c0be --- /dev/null +++ b/static/bidder-params/roulax.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Roulax Adapter Params", + "description": "A schema which validates params accepted by the Roulax adapter", + "type": "object", + "properties": { + "PId": { + "type": "string", + "minLength": 1, + "description": "PID" + }, + "PublisherPath": { + "type": "string", + "minLength": 1, + "description": "PublisherPath" + } + }, + "required": ["Pid", "PublisherPath"] +} \ No newline at end of file