diff --git a/adapters/melozen/melozen.go b/adapters/melozen/melozen.go
new file mode 100644
index 00000000000..cb76274865b
--- /dev/null
+++ b/adapters/melozen/melozen.go
@@ -0,0 +1,185 @@
+package melozen
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strings"
+ "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
+}
+
+// Builder builds a new instance of the MeloZen 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{
+ endpointTemplate: template,
+ }
+
+ return bidder, nil
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+ var requests []*adapters.RequestData
+ var errors []error
+ headers := http.Header{}
+ headers.Add("Content-Type", "application/json;charset=utf-8")
+ headers.Add("Accept", "application/json")
+
+ requestCopy := *request
+ for _, imp := range request.Imp {
+ // Extract Melozen Params
+ var strImpExt adapters.ExtImpBidder
+ if err := json.Unmarshal(imp.Ext, &strImpExt); err != nil {
+ errors = append(errors, err)
+ continue
+ }
+ var strImpParams openrtb_ext.ImpExtMeloZen
+ if err := json.Unmarshal(strImpExt.Bidder, &strImpParams); err != nil {
+ errors = append(errors, err)
+ continue
+ }
+
+ url, err := macros.ResolveMacros(a.endpointTemplate, macros.EndpointTemplateParams{PublisherID: strImpParams.PubId})
+ if err != nil {
+ errors = append(errors, err)
+ continue
+ }
+ // Convert Floor into USD
+ if imp.BidFloor > 0 && imp.BidFloorCur != "" && !strings.EqualFold(imp.BidFloorCur, "USD") {
+ convertedValue, err := reqInfo.ConvertCurrency(imp.BidFloor, imp.BidFloorCur, "USD")
+ if err != nil {
+ errors = append(errors, err)
+ continue
+ }
+ imp.BidFloorCur = "USD"
+ imp.BidFloor = convertedValue
+ }
+
+ impressionsByMediaType, err := splitImpressionsByMediaType(&imp)
+ if err != nil {
+ errors = append(errors, err)
+ continue
+ }
+
+ for _, impression := range impressionsByMediaType {
+ requestCopy.Imp = []openrtb2.Imp{impression}
+
+ requestJSON, err := json.Marshal(requestCopy)
+ if err != nil {
+ errors = append(errors, err)
+ continue
+ }
+
+ requestData := &adapters.RequestData{
+ Method: "POST",
+ Uri: url,
+ Body: requestJSON,
+ Headers: headers,
+ ImpIDs: openrtb_ext.GetImpIDs(requestCopy.Imp),
+ }
+ requests = append(requests, requestData)
+ }
+ }
+
+ return requests, errors
+}
+
+func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *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 bidReq openrtb2.BidRequest
+ if err := json.Unmarshal(requestData.Body, &bidReq); err != nil {
+ return nil, []error{err}
+ }
+
+ var bidResp openrtb2.BidResponse
+ if err := json.Unmarshal(response.Body, &bidResp); err != nil {
+ return nil, []error{err}
+ }
+
+ bidderResponse := adapters.NewBidderResponse()
+ var errors []error
+ for _, seatBid := range bidResp.SeatBid {
+ for i := range seatBid.Bid {
+ bid := &seatBid.Bid[i]
+ bidType, err := getMediaTypeForBid(*bid)
+ if err != nil {
+ errors = append(errors, err)
+ continue
+ }
+
+ bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{
+ BidType: bidType,
+ Bid: bid,
+ })
+ }
+ }
+ return bidderResponse, errors
+}
+
+func splitImpressionsByMediaType(impression *openrtb2.Imp) ([]openrtb2.Imp, error) {
+ if impression.Banner == nil && impression.Native == nil && impression.Video == nil {
+ return nil, &errortypes.BadInput{Message: "Invalid MediaType. MeloZen only supports Banner, Video and Native."}
+ }
+
+ impressions := make([]openrtb2.Imp, 0, 2)
+
+ if impression.Banner != nil {
+ impCopy := *impression
+ impCopy.Video = nil
+ impCopy.Native = nil
+ impressions = append(impressions, impCopy)
+ }
+
+ if impression.Video != nil {
+ impCopy := *impression
+ impCopy.Banner = nil
+ impCopy.Native = nil
+ impressions = append(impressions, impCopy)
+ }
+
+ if impression.Native != nil {
+ impCopy := *impression
+ impCopy.Banner = nil
+ impCopy.Video = nil
+ impressions = append(impressions, impCopy)
+ }
+
+ return impressions, nil
+}
+
+func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) {
+
+ if bid.Ext != nil {
+ var bidExt openrtb_ext.ExtBid
+ err := json.Unmarshal(bid.Ext, &bidExt)
+ if err == nil && bidExt.Prebid != nil {
+ return openrtb_ext.ParseBidType(string(bidExt.Prebid.Type))
+ }
+ }
+
+ return "", &errortypes.BadServerResponse{
+ Message: fmt.Sprintf("Failed to parse bid mediatype for impression \"%s\"", bid.ImpID),
+ }
+}
diff --git a/adapters/melozen/melozen_test.go b/adapters/melozen/melozen_test.go
new file mode 100644
index 00000000000..0191ab73182
--- /dev/null
+++ b/adapters/melozen/melozen_test.go
@@ -0,0 +1,30 @@
+package melozen
+
+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.BidderMeloZen, config.Adapter{
+ Endpoint: "https://example.com/rtb/v2/bid?publisher_id={{.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, "melozentest", bidder)
+}
+
+func TestEndpointTemplateMalformed(t *testing.T) {
+ _, buildErr := Builder(openrtb_ext.BidderMeloZen, config.Adapter{
+ Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+
+ assert.Error(t, buildErr)
+}
diff --git a/adapters/melozen/melozentest/exemplary/app-banner.json b/adapters/melozen/melozentest/exemplary/app-banner.json
new file mode 100644
index 00000000000..6cdb82bf5ad
--- /dev/null
+++ b/adapters/melozen/melozentest/exemplary/app-banner.json
@@ -0,0 +1,132 @@
+{
+ "mockBidRequest": {
+ "id": "web-banner",
+ "tmax": 3000,
+ "imp": [
+ {
+ "id": "banner-imp-id",
+ "ext": {
+ "bidder": {
+ "pubId": "386276e072"
+ }
+ },
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ }
+ }
+ ],
+ "app": {
+ "bundle": "com.fake.app",
+ "publisher": {
+ "id": "42",
+ "name": "whatever.pub"
+ }
+ },
+ "device": {
+ "w": 1200,
+ "h": 900
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072",
+ "headers": {
+ "Content-Type": ["application/json;charset=utf-8"],
+ "Accept": ["application/json"]
+ },
+ "body": {
+ "id": "web-banner",
+ "tmax": 3000,
+ "imp": [
+ {
+ "id": "banner-imp-id",
+ "ext": {
+ "bidder": {
+ "pubId": "386276e072"
+ }
+ },
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ }
+ }
+ ],
+ "device": {
+ "w": 1200,
+ "h": 900
+ },
+ "app": {
+ "bundle": "com.fake.app",
+ "publisher": {
+ "id": "42",
+ "name": "whatever.pub"
+ }
+ }
+ },
+ "impIDs":["banner-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "web-banner",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "web-banner",
+ "impid": "banner-imp-id",
+ "crid": "some-creative-id",
+ "adm": "
Ad
",
+ "price": 20,
+ "w": 300,
+ "h": 250,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "web-banner",
+ "impid": "banner-imp-id",
+ "crid": "some-creative-id",
+ "adm": "Ad
",
+ "price": 20,
+ "w": 300,
+ "h": 250,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ }
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+ }
+
\ No newline at end of file
diff --git a/adapters/melozen/melozentest/exemplary/app-native.json b/adapters/melozen/melozentest/exemplary/app-native.json
new file mode 100644
index 00000000000..f93abd44bea
--- /dev/null
+++ b/adapters/melozen/melozentest/exemplary/app-native.json
@@ -0,0 +1,100 @@
+{
+ "mockBidRequest": {
+ "id": "web-native",
+ "imp": [
+ {
+ "id": "native-imp-id",
+ "ext": {
+ "bidder": {
+ "pubId": "386276e072"
+ }
+ },
+ "native": {
+ "ver": "1.2",
+ "request": "placeholder request"
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072",
+ "headers": {
+ "Content-Type": ["application/json;charset=utf-8"],
+ "Accept": ["application/json"]
+ },
+ "body": {
+ "id": "web-native",
+ "imp": [
+ {
+ "id": "native-imp-id",
+ "ext": {
+ "bidder": {
+ "pubId": "386276e072"
+ }
+ },
+ "native": {
+ "ver": "1.2",
+ "request": "placeholder request"
+ }
+ }
+ ]
+ },
+ "impIDs":["native-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "web-native",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "web-native",
+ "impid": "native-imp-id",
+ "crid": "some-creative-id",
+ "adm": "Ad
",
+ "price": 20,
+ "w": 300,
+ "h": 250,
+ "ext": {
+ "prebid": {
+ "type": "native"
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "web-native",
+ "impid": "native-imp-id",
+ "crid": "some-creative-id",
+ "adm": "Ad
",
+ "price": 20,
+ "w": 300,
+ "h": 250,
+ "ext": {
+ "prebid": {
+ "type": "native"
+ }
+ }
+ },
+ "type": "native"
+ }
+ ]
+ }
+ ]
+ }
+
\ No newline at end of file
diff --git a/adapters/melozen/melozentest/exemplary/app-video.json b/adapters/melozen/melozentest/exemplary/app-video.json
new file mode 100644
index 00000000000..3d913c43e44
--- /dev/null
+++ b/adapters/melozen/melozentest/exemplary/app-video.json
@@ -0,0 +1,137 @@
+{
+ "mockBidRequest": {
+ "id": "app-video",
+ "tmax": 3000,
+ "imp": [
+ {
+ "id": "video-imp-id",
+ "ext": {
+ "bidder": {
+ "pubId": "386276e072"
+ }
+ },
+ "video": {
+ "w": 640,
+ "h": 480,
+ "mimes": [
+ "video/mp4"
+ ],
+ "placement": 1
+ }
+ }
+ ],
+ "app": {
+ "bundle": "com.fake.app",
+ "publisher": {
+ "id": "42",
+ "name": "whatever.pub"
+ }
+ },
+ "device": {
+ "w": 1200,
+ "h": 900
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072",
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ]
+ },
+ "body": {
+ "id": "app-video",
+ "tmax": 3000,
+ "imp": [
+ {
+ "id": "video-imp-id",
+ "ext": {
+ "bidder": {
+ "pubId": "386276e072"
+ }
+ },
+ "video": {
+ "w": 640,
+ "h": 480,
+ "mimes": [
+ "video/mp4"
+ ],
+ "placement": 1
+ }
+ }
+ ],
+ "app": {
+ "bundle": "com.fake.app",
+ "publisher": {
+ "id": "42",
+ "name": "whatever.pub"
+ }
+ },
+ "device": {
+ "w": 1200,
+ "h": 900
+ }
+ },
+ "impIDs": [
+ "video-imp-id"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "app-video",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "app-video",
+ "impid": "video-imp-id",
+ "crid": "some-creative-id",
+ "adm": "TAG",
+ "price": 20,
+ "w": 640,
+ "h": 480,
+ "ext": {
+ "prebid": {
+ "type": "video"
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "app-video",
+ "impid": "video-imp-id",
+ "crid": "some-creative-id",
+ "adm": "TAG",
+ "price": 20,
+ "w": 640,
+ "h": 480,
+ "ext": {
+ "prebid": {
+ "type": "video"
+ }
+ }
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/melozen/melozentest/exemplary/multi-imps.json b/adapters/melozen/melozentest/exemplary/multi-imps.json
new file mode 100644
index 00000000000..916c74cb685
--- /dev/null
+++ b/adapters/melozen/melozentest/exemplary/multi-imps.json
@@ -0,0 +1,239 @@
+{
+ "mockBidRequest": {
+ "id": "web-banner",
+ "tmax": 3000,
+ "imp": [
+ {
+ "id": "banner-imp-id-1",
+ "ext": {
+ "bidder": {
+ "pubId": "386276e072"
+ }
+ },
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ }
+ },
+ {
+ "id": "banner-imp-id-2",
+ "ext": {
+ "bidder": {
+ "pubId": "386276e072"
+ }
+ },
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 600
+ }
+ ]
+ }
+ }
+ ],
+ "site": {
+ "publisher": {
+ "id": "1"
+ },
+ "page": "https://some-site.com",
+ "ref": "https://some-site.com"
+ },
+ "device": {
+ "w": 1200,
+ "h": 900
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072",
+ "headers": {
+ "Content-Type": ["application/json;charset=utf-8"],
+ "Accept": ["application/json"]
+ },
+ "body": {
+ "id": "web-banner",
+ "tmax": 3000,
+ "imp": [
+ {
+ "id": "banner-imp-id-1",
+ "ext": {
+ "bidder": {
+ "pubId": "386276e072"
+ }
+ },
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ }
+ }
+ ],
+ "site": {
+ "publisher": {
+ "id": "1"
+ },
+ "page": "https://some-site.com",
+ "ref": "https://some-site.com"
+ },
+ "device": {
+ "w": 1200,
+ "h": 900
+ }
+ },
+ "impIDs":["banner-imp-id-1"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "web-banner",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "web-banner",
+ "impid": "banner-imp-id-1",
+ "crid": "some-creative-id",
+ "adm": "Ad
",
+ "price": 20,
+ "w": 300,
+ "h": 250,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ {
+ "expectedRequest": {
+ "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072",
+ "headers": {
+ "Content-Type": ["application/json;charset=utf-8"],
+ "Accept": ["application/json"]
+ },
+ "body": {
+ "id": "web-banner",
+ "tmax": 3000,
+ "imp": [
+ {
+ "id": "banner-imp-id-2",
+ "ext": {
+ "bidder": {
+ "pubId": "386276e072"
+ }
+ },
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 600
+ }
+ ]
+ }
+ }
+ ],
+ "site": {
+ "publisher": {
+ "id": "1"
+ },
+ "page": "https://some-site.com",
+ "ref": "https://some-site.com"
+ },
+ "device": {
+ "w": 1200,
+ "h": 900
+ }
+ },
+ "impIDs":["banner-imp-id-2"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "web-banner",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "web-banner",
+ "impid": "banner-imp-id-2",
+ "crid": "some-creative-id",
+ "adm": "Ad
",
+ "price": 20,
+ "w": 300,
+ "h": 600,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "web-banner",
+ "impid": "banner-imp-id-1",
+ "crid": "some-creative-id",
+ "adm": "Ad
",
+ "price": 20,
+ "w": 300,
+ "h": 250,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ }
+ },
+ "type": "banner"
+ }
+ ]
+ },
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "web-banner",
+ "impid": "banner-imp-id-2",
+ "crid": "some-creative-id",
+ "adm": "Ad
",
+ "price": 20,
+ "w": 300,
+ "h": 600,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ }
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/melozen/melozentest/exemplary/web-banner.json b/adapters/melozen/melozentest/exemplary/web-banner.json
new file mode 100644
index 00000000000..0439baa1033
--- /dev/null
+++ b/adapters/melozen/melozentest/exemplary/web-banner.json
@@ -0,0 +1,138 @@
+{
+ "mockBidRequest": {
+ "id": "web-banner",
+ "tmax": 3000,
+ "imp": [
+ {
+ "id": "baner-imp-id",
+ "ext": {
+ "bidder": {
+ "pubId": "386276e072"
+ }
+ },
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ }
+ }
+ ],
+ "test": 0,
+ "site": {
+ "publisher": {
+ "id": "1"
+ },
+ "page": "https://some-site.com",
+ "ref": "https://some-site.com"
+ },
+ "device": {
+ "w": 1200,
+ "h": 900
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072",
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ]
+ },
+ "body": {
+ "id": "web-banner",
+ "tmax": 3000,
+ "imp": [
+ {
+ "id": "baner-imp-id",
+ "ext": {
+ "bidder": {
+ "pubId": "386276e072"
+ }
+ },
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ }
+ }
+ ],
+ "site": {
+ "publisher": {
+ "id": "1"
+ },
+ "page": "https://some-site.com",
+ "ref": "https://some-site.com"
+ },
+ "device": {
+ "w": 1200,
+ "h": 900
+ }
+ },
+ "impIDs": [
+ "baner-imp-id"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "web-banner",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "web-banner",
+ "impid": "baner-imp-id",
+ "crid": "some-creative-id",
+ "adm": "Ad
",
+ "price": 20,
+ "w": 300,
+ "h": 250,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "web-banner",
+ "impid": "baner-imp-id",
+ "crid": "some-creative-id",
+ "adm": "Ad
",
+ "price": 20,
+ "w": 300,
+ "h": 250,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ }
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/melozen/melozentest/exemplary/web-video.json b/adapters/melozen/melozentest/exemplary/web-video.json
new file mode 100644
index 00000000000..b4f179bdc55
--- /dev/null
+++ b/adapters/melozen/melozentest/exemplary/web-video.json
@@ -0,0 +1,129 @@
+{
+ "mockBidRequest": {
+ "id": "web-video",
+ "tmax": 3000,
+ "imp": [
+ {
+ "id": "video-imp-id",
+ "ext": {
+ "bidder": {
+ "pubId": "386276e072"
+ }
+ },
+ "video": {
+ "w": 640,
+ "h": 480,
+ "mimes": ["video/mp4"],
+ "placement": 1
+ }
+ }
+ ],
+ "test": 0,
+ "site": {
+ "publisher": {
+ "id": "1"
+ },
+ "page": "https://some-site.com",
+ "ref": "https://some-site.com"
+ },
+ "device": {
+ "w": 1200,
+ "h": 900
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072",
+ "headers": {
+ "Content-Type": ["application/json;charset=utf-8"],
+ "Accept": ["application/json"]
+ },
+ "body": {
+ "id": "web-video",
+ "tmax": 3000,
+ "imp": [
+ {
+ "id": "video-imp-id",
+ "ext": {
+ "bidder": {
+ "pubId": "386276e072"
+ }
+ },
+ "video": {
+ "w": 640,
+ "h": 480,
+ "mimes": ["video/mp4"],
+ "placement": 1
+ }
+ }
+ ],
+ "site": {
+ "publisher": {
+ "id": "1"
+ },
+ "page": "https://some-site.com",
+ "ref": "https://some-site.com"
+ },
+ "device": {
+ "w": 1200,
+ "h": 900
+ }
+ },
+ "impIDs":["video-imp-id"]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "web-video",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "web-video",
+ "impid": "video-imp-id",
+ "crid": "some-creative-id",
+ "adm": "TAG",
+ "price": 20,
+ "w": 640,
+ "h": 480,
+ "ext": {
+ "prebid": {
+ "type": "video"
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "web-video",
+ "impid": "video-imp-id",
+ "crid": "some-creative-id",
+ "adm": "TAG",
+ "price": 20,
+ "w": 640,
+ "h": 480,
+ "ext": {
+ "prebid": {
+ "type": "video"
+ }
+ }
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+ }
+
\ No newline at end of file
diff --git a/adapters/melozen/melozentest/supplemental/bad-media-type-request.json b/adapters/melozen/melozentest/supplemental/bad-media-type-request.json
new file mode 100644
index 00000000000..f6c17a70b8f
--- /dev/null
+++ b/adapters/melozen/melozentest/supplemental/bad-media-type-request.json
@@ -0,0 +1,28 @@
+{
+ "mockBidRequest": {
+ "id": "unsupported-request",
+ "imp": [
+ {
+ "id": "unsupported-imp",
+ "unupported": {
+ },
+ "ext": {
+ "bidder": {
+ "pubId": "386276e072"
+ }
+ }
+ }
+ ],
+ "site": {
+ "id": "siteID"
+ }
+ },
+
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "Invalid MediaType. MeloZen only supports Banner, Video and Native.",
+ "comparison": "literal"
+ }
+ ]
+ }
+
\ No newline at end of file
diff --git a/adapters/melozen/melozentest/supplemental/no-fill.json b/adapters/melozen/melozentest/supplemental/no-fill.json
new file mode 100644
index 00000000000..7dd600a72b6
--- /dev/null
+++ b/adapters/melozen/melozentest/supplemental/no-fill.json
@@ -0,0 +1,90 @@
+{
+ "mockBidRequest": {
+ "id": "web-banner",
+ "tmax": 3000,
+ "imp": [
+ {
+ "id": "banner-imp-id",
+ "ext": {
+ "bidder": {
+ "pubId": "386276e072"
+ }
+ },
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ }
+ }
+ ],
+ "app": {
+ "bundle": "com.fake.app",
+ "publisher": {
+ "id": "42",
+ "name": "whatever.pub"
+ }
+ },
+ "device": {
+ "w": 1200,
+ "h": 900
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072",
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ]
+ },
+ "body": {
+ "id": "web-banner",
+ "tmax": 3000,
+ "imp": [
+ {
+ "id": "banner-imp-id",
+ "ext": {
+ "bidder": {
+ "pubId": "386276e072"
+ }
+ },
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ }
+ }
+ ],
+ "device": {
+ "w": 1200,
+ "h": 900
+ },
+ "app": {
+ "bundle": "com.fake.app",
+ "publisher": {
+ "id": "42",
+ "name": "whatever.pub"
+ }
+ }
+ },
+ "impIDs": [
+ "banner-imp-id"
+ ]
+ },
+ "mockResponse": {
+ "status": 204
+ }
+ }
+ ],
+ "expectedMakeBidsErrors": []
+}
\ No newline at end of file
diff --git a/adapters/melozen/melozentest/supplemental/response-status-400.json b/adapters/melozen/melozentest/supplemental/response-status-400.json
new file mode 100644
index 00000000000..969875b86ec
--- /dev/null
+++ b/adapters/melozen/melozentest/supplemental/response-status-400.json
@@ -0,0 +1,95 @@
+{
+ "mockBidRequest": {
+ "id": "web-banner",
+ "tmax": 3000,
+ "imp": [
+ {
+ "id": "banner-imp-id",
+ "ext": {
+ "bidder": {
+ "pubId": "386276e072"
+ }
+ },
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ }
+ }
+ ],
+ "app": {
+ "bundle": "com.fake.app",
+ "publisher": {
+ "id": "42",
+ "name": "whatever.pub"
+ }
+ },
+ "device": {
+ "w": 1200,
+ "h": 900
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072",
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ]
+ },
+ "body": {
+ "id": "web-banner",
+ "tmax": 3000,
+ "imp": [
+ {
+ "id": "banner-imp-id",
+ "ext": {
+ "bidder": {
+ "pubId": "386276e072"
+ }
+ },
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ }
+ }
+ ],
+ "device": {
+ "w": 1200,
+ "h": 900
+ },
+ "app": {
+ "bundle": "com.fake.app",
+ "publisher": {
+ "id": "42",
+ "name": "whatever.pub"
+ }
+ }
+ },
+ "impIDs": [
+ "banner-imp-id"
+ ]
+ },
+ "mockResponse": {
+ "status": 400
+ }
+ }
+ ],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 400. Run with request.debug = 1 for more info",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/melozen/melozentest/supplemental/response-status-not-200.json b/adapters/melozen/melozentest/supplemental/response-status-not-200.json
new file mode 100644
index 00000000000..9b26ee58091
--- /dev/null
+++ b/adapters/melozen/melozentest/supplemental/response-status-not-200.json
@@ -0,0 +1,84 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ },
+ {
+ "w": 300,
+ "h": 600
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "pubId": "386276e072"
+ }
+ }
+ }
+ ],
+ "app": {
+ "id": "1",
+ "bundle": "com.test.testapplication"
+ },
+ "device": {
+ "ip": "123.123.123.123",
+ "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+ }
+ },
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ },
+ {
+ "w": 300,
+ "h": 600
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "pubId": "386276e072"
+ }
+ }
+ }
+ ],
+ "app": {
+ "id": "1",
+ "bundle": "com.test.testapplication"
+ },
+ "device": {
+ "ip": "123.123.123.123",
+ "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+ }
+ },
+ "impIDs":["test-imp-id"]
+ },
+ "mockResponse": {
+ "status": 404,
+ "body": {}
+ }
+ }],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 404. Run with request.debug = 1 for more info",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/melozen/melozentest/supplemental/wrong-bid-ext.json b/adapters/melozen/melozentest/supplemental/wrong-bid-ext.json
new file mode 100644
index 00000000000..b6a1c1f7268
--- /dev/null
+++ b/adapters/melozen/melozentest/supplemental/wrong-bid-ext.json
@@ -0,0 +1,85 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "video": {
+ "w": 900,
+ "h": 250,
+ "mimes": [
+ "video/x-flv",
+ "video/mp4"
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "pubId": "386276e072"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072",
+ "headers": {
+ "Content-Type": ["application/json;charset=utf-8"],
+ "Accept": ["application/json"]
+ },
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "video": {
+ "w": 900,
+ "h": 250,
+ "mimes": [
+ "video/x-flv",
+ "video/mp4"
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "pubId": "386276e072"
+ }
+ }
+ }
+ ]
+ },
+ "impIDs": [
+ "test-imp-id"
+ ]
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "test-bid-id",
+ "impid": "another-imp-id",
+ "price": 3.5,
+ "w": 900,
+ "h": 250,
+ "ext": {}
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [{"currency":"USD","bids":[]}],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Failed to parse bid mediatype for impression \"another-imp-id\"",
+ "comparison": "regex"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/melozen/params_test.go b/adapters/melozen/params_test.go
new file mode 100644
index 00000000000..7e1be7f0db0
--- /dev/null
+++ b/adapters/melozen/params_test.go
@@ -0,0 +1,50 @@
+package melozen
+
+import (
+ "encoding/json"
+ "testing"
+
+ "github.com/prebid/prebid-server/v2/openrtb_ext"
+)
+
+func TestValidParams(t *testing.T) {
+ validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+ if err != nil {
+ t.Fatalf("Failed to fetch the JSON schema. %v", err)
+ }
+
+ for _, p := range validParams {
+ if err := validator.Validate(openrtb_ext.BidderMeloZen, json.RawMessage(p)); err != nil {
+ t.Errorf("Schema rejected valid params: %s", p)
+ }
+ }
+}
+
+func TestInvalidParams(t *testing.T) {
+ validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+ if err != nil {
+ t.Fatalf("Failed to fetch the JSON schema. %v", err)
+ }
+
+ for _, p := range invalidParams {
+ if err := validator.Validate(openrtb_ext.BidderMeloZen, json.RawMessage(p)); err == nil {
+ t.Errorf("Schema allowed invalid params: %s", p)
+ }
+ }
+}
+
+var validParams = []string{
+ `{"pubId": "12345"}`,
+}
+
+var invalidParams = []string{
+ ``,
+ `null`,
+ `true`,
+ `5`,
+ `4.2`,
+ `[]`,
+ `{}`,
+ `{"pubId": ""}`,
+ `{"pubId": 12345}`,
+}
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index b79d03a8afd..6e5c30652c4 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -129,6 +129,7 @@ import (
"github.com/prebid/prebid-server/v2/adapters/marsmedia"
"github.com/prebid/prebid-server/v2/adapters/mediago"
"github.com/prebid/prebid-server/v2/adapters/medianet"
+ "github.com/prebid/prebid-server/v2/adapters/melozen"
"github.com/prebid/prebid-server/v2/adapters/metax"
"github.com/prebid/prebid-server/v2/adapters/mgid"
"github.com/prebid/prebid-server/v2/adapters/mgidX"
@@ -353,6 +354,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
openrtb_ext.BidderMediafuse: appnexus.Builder,
openrtb_ext.BidderMediaGo: mediago.Builder,
openrtb_ext.BidderMedianet: medianet.Builder,
+ openrtb_ext.BidderMeloZen: melozen.Builder,
openrtb_ext.BidderMetaX: metax.Builder,
openrtb_ext.BidderMgid: mgid.Builder,
openrtb_ext.BidderMgidX: mgidX.Builder,
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index 46d70c7f998..286361b6df7 100644
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -147,6 +147,7 @@ var coreBidderNames []BidderName = []BidderName{
BidderMediafuse,
BidderMediaGo,
BidderMedianet,
+ BidderMeloZen,
BidderMetaX,
BidderMgid,
BidderMgidX,
@@ -468,6 +469,7 @@ const (
BidderMediafuse BidderName = "mediafuse"
BidderMediaGo BidderName = "mediago"
BidderMedianet BidderName = "medianet"
+ BidderMeloZen BidderName = "melozen"
BidderMetaX BidderName = "metax"
BidderMgid BidderName = "mgid"
BidderMgidX BidderName = "mgidX"
diff --git a/openrtb_ext/imp_melozen.go b/openrtb_ext/imp_melozen.go
new file mode 100644
index 00000000000..598df6a28e9
--- /dev/null
+++ b/openrtb_ext/imp_melozen.go
@@ -0,0 +1,5 @@
+package openrtb_ext
+
+type ImpExtMeloZen struct {
+ PubId string `json:"pubId"`
+}
diff --git a/static/bidder-info/melozen.yaml b/static/bidder-info/melozen.yaml
new file mode 100644
index 00000000000..391e0a8d43b
--- /dev/null
+++ b/static/bidder-info/melozen.yaml
@@ -0,0 +1,19 @@
+# We have the following regional endpoint domains: us-east and us-west
+# Please deploy this config in each of your datacenters with the appropriate regional subdomain
+endpoint: "https://prebid.melozen.com/rtb/v2/bid?publisher_id={{.PublisherID}}"
+endpointCompression: gzip
+geoscope:
+ - global
+maintainer:
+ email: DSP@melodong.com
+capabilities:
+ site:
+ mediaTypes:
+ - banner
+ - video
+ - native
+ app:
+ mediaTypes:
+ - banner
+ - video
+ - native
\ No newline at end of file
diff --git a/static/bidder-params/melozen.json b/static/bidder-params/melozen.json
new file mode 100644
index 00000000000..6b5cef5b3fd
--- /dev/null
+++ b/static/bidder-params/melozen.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "MeloZen Adapter Params",
+ "description": "A schema which validates params accepted by the MeloZen adapter",
+ "type": "object",
+ "properties": {
+ "pubId": {
+ "type": "string",
+ "minLength": 1,
+ "description": "The unique identifier for the publisher."
+ }
+ },
+ "required": ["pubId"]
+}