From 19c4843f8602c401053a3b4662f84de2aa155515 Mon Sep 17 00:00:00 2001 From: v0idxyz <58184010+v0idxyz@users.noreply.github.com> Date: Tue, 28 Apr 2026 12:23:09 +0200 Subject: [PATCH 1/4] added revantage files --- adapters/revantage/params_test.go | 55 ++++ adapters/revantage/revantage.go | 264 ++++++++++++++++++ adapters/revantage/revantage_test.go | 25 ++ .../exemplary/multi-feed-split.json | 110 ++++++++ .../exemplary/multi-imp-same-feed.json | 94 +++++++ .../exemplary/simple-banner.json | 148 ++++++++++ .../revantagetest/exemplary/simple-video.json | 117 ++++++++ .../supplemental/204-response.json | 40 +++ .../supplemental/400-response.json | 45 +++ .../supplemental/missing-feedid.json | 22 ++ openrtb_ext/imp_revantage.go | 12 + static/bidder-info/revantage.yaml | 22 ++ static/bidder-params/revantage.json | 22 ++ 13 files changed, 976 insertions(+) create mode 100644 adapters/revantage/params_test.go create mode 100644 adapters/revantage/revantage.go create mode 100644 adapters/revantage/revantage_test.go create mode 100644 adapters/revantage/revantagetest/exemplary/multi-feed-split.json create mode 100644 adapters/revantage/revantagetest/exemplary/multi-imp-same-feed.json create mode 100644 adapters/revantage/revantagetest/exemplary/simple-banner.json create mode 100644 adapters/revantage/revantagetest/exemplary/simple-video.json create mode 100644 adapters/revantage/revantagetest/supplemental/204-response.json create mode 100644 adapters/revantage/revantagetest/supplemental/400-response.json create mode 100644 adapters/revantage/revantagetest/supplemental/missing-feedid.json create mode 100644 openrtb_ext/imp_revantage.go create mode 100644 static/bidder-info/revantage.yaml create mode 100644 static/bidder-params/revantage.json diff --git a/adapters/revantage/params_test.go b/adapters/revantage/params_test.go new file mode 100644 index 00000000000..41cfb6af72c --- /dev/null +++ b/adapters/revantage/params_test.go @@ -0,0 +1,55 @@ +package revantage + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/v3/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-schemas: %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderRevantage, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected revantage params that should be valid: %s\nError: %s", validParam, err) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas: %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderRevantage, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema accepted revantage params that should be invalid: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"feedId":"feed-abc"}`, + `{"feedId":"feed-abc","placementId":"plc-1"}`, + `{"feedId":"feed-abc","placementId":"plc-1","publisherId":"pub-1"}`, + `{"feedId":"x"}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `[]`, + `{}`, + `{"placementId":"plc-1"}`, + `{"feedId":""}`, + `{"feedId":123}`, + `{"feedId":null}`, + `{"feedId":"feed-abc","placementId":42}`, +} diff --git a/adapters/revantage/revantage.go b/adapters/revantage/revantage.go new file mode 100644 index 00000000000..532b97c00b8 --- /dev/null +++ b/adapters/revantage/revantage.go @@ -0,0 +1,264 @@ +package revantage + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "strings" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v3/adapters" + "github.com/prebid/prebid-server/v3/config" + "github.com/prebid/prebid-server/v3/errortypes" + "github.com/prebid/prebid-server/v3/openrtb_ext" +) + +type adapter struct { + endpoint string +} + +// Builder builds a new instance of the Revantage adapter for the given bidder with the given config. +func Builder(_ openrtb_ext.BidderName, cfg config.Adapter, _ config.Server) (adapters.Bidder, error) { + return &adapter{endpoint: cfg.Endpoint}, nil +} + +// rewrittenImpExt is the shape the Revantage endpoint expects on imp.ext. +// It mirrors the public Prebid.js client adapter (revantageBidAdapter.js) so the +// upstream endpoint can be a single code path for both client- and server-side +// integrations. +type rewrittenImpExt struct { + FeedID string `json:"feedId"` + Bidder rewrittenImpBidder `json:"bidder"` +} + +type rewrittenImpBidder struct { + PlacementID string `json:"placementId,omitempty"` + PublisherID string `json:"publisherId,omitempty"` +} + +// MakeRequests converts an OpenRTB bid request into one or more HTTP calls to the Revantage endpoint. +// +// Impressions are grouped by feedId. Each group becomes a separate HTTP call so that a single +// auction can serve multiple feeds without conflict — the endpoint's `?feed=` query param must +// match the feedId used in every imp it carries. +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + if request == nil || len(request.Imp) == 0 { + return nil, []error{&errortypes.BadInput{Message: "no impressions in bid request"}} + } + + // Preserve insertion order of feed groups for deterministic test output. + type group struct { + imps []openrtb2.Imp + } + groups := make(map[string]*group) + feedOrder := make([]string, 0) + var errs []error + + for i := range request.Imp { + imp := request.Imp[i] + feedID, err := rewriteImpExt(&imp) + if err != nil { + errs = append(errs, err) + continue + } + g, ok := groups[feedID] + if !ok { + g = &group{} + groups[feedID] = g + feedOrder = append(feedOrder, feedID) + } + g.imps = append(g.imps, imp) + } + + if len(groups) == 0 { + return nil, errs + } + + requests := make([]*adapters.RequestData, 0, len(groups)) + for _, feedID := range feedOrder { + imps := groups[feedID].imps + + // Shallow copy the request and replace imps with this feed's slice. + reqCopy := *request + reqCopy.Imp = imps + + body, err := json.Marshal(reqCopy) + if err != nil { + errs = append(errs, fmt.Errorf("failed to marshal request for feed %s: %w", feedID, err)) + continue + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + requests = append(requests, &adapters.RequestData{ + Method: http.MethodPost, + Uri: a.endpoint + "?feed=" + url.QueryEscape(feedID), + Body: body, + Headers: headers, + ImpIDs: collectImpIDs(imps), + }) + } + + return requests, errs +} + +// rewriteImpExt validates the bidder params on a single imp and rewrites imp.ext into the +// shape the Revantage endpoint expects. Returns the resolved feedId. +func rewriteImpExt(imp *openrtb2.Imp) (string, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("imp %s: invalid imp.ext: %s", imp.ID, err.Error()), + } + } + + var revantageExt openrtb_ext.ImpExtRevantage + if err := json.Unmarshal(bidderExt.Bidder, &revantageExt); err != nil { + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("imp %s: invalid imp.ext.bidder: %s", imp.ID, err.Error()), + } + } + + feedID := strings.TrimSpace(revantageExt.FeedID) + if feedID == "" { + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("imp %s: missing required param feedId", imp.ID), + } + } + + rewritten := rewrittenImpExt{ + FeedID: feedID, + Bidder: rewrittenImpBidder{ + PlacementID: revantageExt.PlacementID, + PublisherID: revantageExt.PublisherID, + }, + } + extBytes, err := json.Marshal(rewritten) + if err != nil { + return "", fmt.Errorf("imp %s: failed to marshal rewritten ext: %w", imp.ID, err) + } + imp.Ext = extBytes + + return feedID, nil +} + +func collectImpIDs(imps []openrtb2.Imp) []string { + ids := make([]string, len(imps)) + for i, imp := range imps { + ids[i] = imp.ID + } + return ids +} + +// MakeBids parses the upstream Revantage response into typed Prebid 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 bidResp openrtb2.BidResponse + if err := json.Unmarshal(responseData.Body, &bidResp); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: "invalid bid response: " + err.Error(), + }} + } + + if len(bidResp.SeatBid) == 0 { + return nil, nil + } + + response := adapters.NewBidderResponse() + if bidResp.Cur != "" { + response.Currency = bidResp.Cur + } else { + response.Currency = "USD" + } + + var errs []error + for _, seat := range bidResp.SeatBid { + for i := range seat.Bid { + bid := &seat.Bid[i] + mt, err := resolveMediaType(bid, request.Imp) + if err != nil { + errs = append(errs, err) + continue + } + response.Bids = append(response.Bids, &adapters.TypedBid{ + Bid: bid, + BidType: mt, + Seat: openrtb_ext.BidderName(seat.Seat), + }) + } + } + return response, errs +} + +// resolveMediaType picks banner or video based on (in priority order): +// 1. bid.mtype (oRTB 2.6) +// 2. bid.ext.mediaType +// 3. VAST shape detection on bid.adm +// 4. The single configured media type on the originating imp +func resolveMediaType(bid *openrtb2.Bid, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { + switch bid.MType { + case openrtb2.MarkupBanner: + return openrtb_ext.BidTypeBanner, nil + case openrtb2.MarkupVideo: + return openrtb_ext.BidTypeVideo, nil + } + + if len(bid.Ext) > 0 { + var ext struct { + MediaType string `json:"mediaType"` + } + if err := json.Unmarshal(bid.Ext, &ext); err == nil { + switch strings.ToLower(ext.MediaType) { + case "banner": + return openrtb_ext.BidTypeBanner, nil + case "video": + return openrtb_ext.BidTypeVideo, nil + } + } + } + + if isVastMarkup(bid.AdM) { + return openrtb_ext.BidTypeVideo, nil + } + + for _, imp := range imps { + if imp.ID != bid.ImpID { + continue + } + hasBanner := imp.Banner != nil + hasVideo := imp.Video != nil + switch { + case hasVideo && !hasBanner: + return openrtb_ext.BidTypeVideo, nil + case hasBanner && !hasVideo: + return openrtb_ext.BidTypeBanner, nil + case hasBanner && hasVideo: + // Multi-format with no explicit signal — default to banner. The Revantage + // endpoint should set mtype or ext.mediaType in this case; this is a fallback. + return openrtb_ext.BidTypeBanner, nil + } + break + } + + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("could not determine media type for bid %s on imp %s", bid.ID, bid.ImpID), + } +} + +func isVastMarkup(adm string) bool { + trimmed := strings.TrimSpace(adm) + if trimmed == "" { + return false + } + upper := strings.ToUpper(trimmed) + return strings.HasPrefix(upper, "1", "crid": "c1", "w": 300, "h": 250, "mtype": 1} + ] + } + ] + } + } + }, + { + "expectedRequest": { + "uri": "https://bid.revantage.io/bid?feed=feed-two", + "body": { + "id": "multi-feed-request", + "imp": [ + { + "id": "imp-b", + "banner": {"w": 728, "h": 90, "format": [{"w": 728, "h": 90}]}, + "ext": {"feedId": "feed-two", "bidder": {}} + } + ], + "site": {"domain": "example.com"}, + "tmax": 1000, + "cur": ["USD"] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "multi-feed-request", + "cur": "USD", + "seatbid": [ + { + "seat": "dsp-2", + "bid": [ + {"id": "b2", "impid": "imp-b", "price": 2.0, "adm": "
2
", "crid": "c2", "w": 728, "h": 90, "mtype": 1} + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": {"id": "b1", "impid": "imp-a", "price": 1.0, "adm": "
1
", "crid": "c1", "w": 300, "h": 250, "mtype": 1}, + "type": "banner", + "seat": "dsp-1" + } + ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": {"id": "b2", "impid": "imp-b", "price": 2.0, "adm": "
2
", "crid": "c2", "w": 728, "h": 90, "mtype": 1}, + "type": "banner", + "seat": "dsp-2" + } + ] + } + ] +} diff --git a/adapters/revantage/revantagetest/exemplary/multi-imp-same-feed.json b/adapters/revantage/revantagetest/exemplary/multi-imp-same-feed.json new file mode 100644 index 00000000000..d25aded04a8 --- /dev/null +++ b/adapters/revantage/revantagetest/exemplary/multi-imp-same-feed.json @@ -0,0 +1,94 @@ +{ + "mockBidRequest": { + "id": "multi-imp-request", + "imp": [ + { + "id": "imp-1", + "banner": {"w": 300, "h": 250, "format": [{"w": 300, "h": 250}]}, + "ext": {"bidder": {"feedId": "feed-shared"}} + }, + { + "id": "imp-2", + "banner": {"w": 728, "h": 90, "format": [{"w": 728, "h": 90}]}, + "ext": {"bidder": {"feedId": "feed-shared"}} + } + ], + "site": {"domain": "example.com"}, + "tmax": 1000, + "cur": ["USD"] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.revantage.io/bid?feed=feed-shared", + "body": { + "id": "multi-imp-request", + "imp": [ + { + "id": "imp-1", + "banner": {"w": 300, "h": 250, "format": [{"w": 300, "h": 250}]}, + "ext": {"feedId": "feed-shared", "bidder": {}} + }, + { + "id": "imp-2", + "banner": {"w": 728, "h": 90, "format": [{"w": 728, "h": 90}]}, + "ext": {"feedId": "feed-shared", "bidder": {}} + } + ], + "site": {"domain": "example.com"}, + "tmax": 1000, + "cur": ["USD"] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "multi-imp-request", + "cur": "USD", + "seatbid": [ + { + "seat": "dsp-a", + "bid": [ + { + "id": "b1", "impid": "imp-1", "price": 1.10, + "adm": "
A
", "crid": "c1", "w": 300, "h": 250, + "adomain": ["a.com"], "mtype": 1 + }, + { + "id": "b2", "impid": "imp-2", "price": 0.80, + "adm": "
B
", "crid": "c2", "w": 728, "h": 90, + "adomain": ["b.com"], "mtype": 1 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "b1", "impid": "imp-1", "price": 1.10, + "adm": "
A
", "crid": "c1", "w": 300, "h": 250, + "adomain": ["a.com"], "mtype": 1 + }, + "type": "banner", + "seat": "dsp-a" + }, + { + "bid": { + "id": "b2", "impid": "imp-2", "price": 0.80, + "adm": "
B
", "crid": "c2", "w": 728, "h": 90, + "adomain": ["b.com"], "mtype": 1 + }, + "type": "banner", + "seat": "dsp-a" + } + ] + } + ] +} diff --git a/adapters/revantage/revantagetest/exemplary/simple-banner.json b/adapters/revantage/revantagetest/exemplary/simple-banner.json new file mode 100644 index 00000000000..b9d25666c9a --- /dev/null +++ b/adapters/revantage/revantagetest/exemplary/simple-banner.json @@ -0,0 +1,148 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "imp-1", + "tagid": "ad-unit-1", + "banner": { + "w": 300, + "h": 250, + "format": [ + {"w": 300, "h": 250}, + {"w": 300, "h": 600} + ] + }, + "bidfloor": 0.5, + "bidfloorcur": "USD", + "ext": { + "bidder": { + "feedId": "feed-abc", + "placementId": "plc-1", + "publisherId": "pub-1" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "https://example.com/article" + }, + "device": { + "ua": "Mozilla/5.0", + "language": "en" + }, + "user": { + "ext": { + "consent": "BOJ/P2HOJ/P2HABABMAAAAAZ+A==" + } + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "tmax": 1000, + "cur": ["USD"] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.revantage.io/bid?feed=feed-abc", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "imp-1", + "tagid": "ad-unit-1", + "banner": { + "w": 300, + "h": 250, + "format": [ + {"w": 300, "h": 250}, + {"w": 300, "h": 600} + ] + }, + "bidfloor": 0.5, + "bidfloorcur": "USD", + "ext": { + "feedId": "feed-abc", + "bidder": { + "placementId": "plc-1", + "publisherId": "pub-1" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "https://example.com/article" + }, + "device": { + "ua": "Mozilla/5.0", + "language": "en" + }, + "user": { + "ext": { + "consent": "BOJ/P2HOJ/P2HABABMAAAAAZ+A==" + } + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "tmax": 1000, + "cur": ["USD"] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "test-dsp", + "bid": [ + { + "id": "bid-1", + "impid": "imp-1", + "price": 1.25, + "adm": "
Banner Ad
", + "crid": "creative-123", + "w": 300, + "h": 250, + "adomain": ["advertiser.com"], + "mtype": 1 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "bid-1", + "impid": "imp-1", + "price": 1.25, + "adm": "
Banner Ad
", + "crid": "creative-123", + "w": 300, + "h": 250, + "adomain": ["advertiser.com"], + "mtype": 1 + }, + "type": "banner", + "seat": "test-dsp" + } + ] + } + ] +} diff --git a/adapters/revantage/revantagetest/exemplary/simple-video.json b/adapters/revantage/revantagetest/exemplary/simple-video.json new file mode 100644 index 00000000000..162e838d695 --- /dev/null +++ b/adapters/revantage/revantagetest/exemplary/simple-video.json @@ -0,0 +1,117 @@ +{ + "mockBidRequest": { + "id": "test-request-video", + "imp": [ + { + "id": "video-imp-1", + "tagid": "video-unit", + "video": { + "mimes": ["video/mp4", "video/webm"], + "w": 640, + "h": 480, + "minduration": 5, + "maxduration": 30, + "protocols": [2, 3, 5, 6], + "api": [1, 2], + "placement": 1, + "linearity": 1 + }, + "ext": { + "bidder": { + "feedId": "feed-video" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "https://example.com/watch" + }, + "tmax": 1500, + "cur": ["USD"] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.revantage.io/bid?feed=feed-video", + "body": { + "id": "test-request-video", + "imp": [ + { + "id": "video-imp-1", + "tagid": "video-unit", + "video": { + "mimes": ["video/mp4", "video/webm"], + "w": 640, + "h": 480, + "minduration": 5, + "maxduration": 30, + "protocols": [2, 3, 5, 6], + "api": [1, 2], + "placement": 1, + "linearity": 1 + }, + "ext": { + "feedId": "feed-video", + "bidder": {} + } + } + ], + "site": { + "domain": "example.com", + "page": "https://example.com/watch" + }, + "tmax": 1500, + "cur": ["USD"] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-video", + "cur": "USD", + "seatbid": [ + { + "seat": "video-dsp", + "bid": [ + { + "id": "video-bid-1", + "impid": "video-imp-1", + "price": 4.50, + "adm": "", + "crid": "video-creative-1", + "w": 640, + "h": 480, + "adomain": ["video-advertiser.com"], + "mtype": 2 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "video-bid-1", + "impid": "video-imp-1", + "price": 4.50, + "adm": "", + "crid": "video-creative-1", + "w": 640, + "h": 480, + "adomain": ["video-advertiser.com"], + "mtype": 2 + }, + "type": "video", + "seat": "video-dsp" + } + ] + } + ] +} diff --git a/adapters/revantage/revantagetest/supplemental/204-response.json b/adapters/revantage/revantagetest/supplemental/204-response.json new file mode 100644 index 00000000000..5da4aa26fe4 --- /dev/null +++ b/adapters/revantage/revantagetest/supplemental/204-response.json @@ -0,0 +1,40 @@ +{ + "mockBidRequest": { + "id": "no-content-request", + "imp": [ + { + "id": "imp-1", + "banner": {"w": 300, "h": 250, "format": [{"w": 300, "h": 250}]}, + "ext": {"bidder": {"feedId": "feed-abc"}} + } + ], + "site": {"domain": "example.com"}, + "tmax": 1000, + "cur": ["USD"] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.revantage.io/bid?feed=feed-abc", + "body": { + "id": "no-content-request", + "imp": [ + { + "id": "imp-1", + "banner": {"w": 300, "h": 250, "format": [{"w": 300, "h": 250}]}, + "ext": {"feedId": "feed-abc", "bidder": {}} + } + ], + "site": {"domain": "example.com"}, + "tmax": 1000, + "cur": ["USD"] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/revantage/revantagetest/supplemental/400-response.json b/adapters/revantage/revantagetest/supplemental/400-response.json new file mode 100644 index 00000000000..a902fc4ffb9 --- /dev/null +++ b/adapters/revantage/revantagetest/supplemental/400-response.json @@ -0,0 +1,45 @@ +{ + "mockBidRequest": { + "id": "bad-request", + "imp": [ + { + "id": "imp-1", + "banner": {"w": 300, "h": 250, "format": [{"w": 300, "h": 250}]}, + "ext": {"bidder": {"feedId": "feed-abc"}} + } + ], + "site": {"domain": "example.com"}, + "tmax": 1000, + "cur": ["USD"] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://bid.revantage.io/bid?feed=feed-abc", + "body": { + "id": "bad-request", + "imp": [ + { + "id": "imp-1", + "banner": {"w": 300, "h": 250, "format": [{"w": 300, "h": 250}]}, + "ext": {"feedId": "feed-abc", "bidder": {}} + } + ], + "site": {"domain": "example.com"}, + "tmax": 1000, + "cur": ["USD"] + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/revantage/revantagetest/supplemental/missing-feedid.json b/adapters/revantage/revantagetest/supplemental/missing-feedid.json new file mode 100644 index 00000000000..9a4693c899b --- /dev/null +++ b/adapters/revantage/revantagetest/supplemental/missing-feedid.json @@ -0,0 +1,22 @@ +{ + "mockBidRequest": { + "id": "missing-feed", + "imp": [ + { + "id": "imp-x", + "banner": {"w": 300, "h": 250, "format": [{"w": 300, "h": 250}]}, + "ext": {"bidder": {}} + } + ], + "site": {"domain": "example.com"}, + "tmax": 1000, + "cur": ["USD"] + }, + "httpCalls": [], + "expectedMakeRequestsErrors": [ + { + "value": "imp imp-x: missing required param feedId", + "comparison": "literal" + } + ] +} diff --git a/openrtb_ext/imp_revantage.go b/openrtb_ext/imp_revantage.go new file mode 100644 index 00000000000..dd4fee2ef4f --- /dev/null +++ b/openrtb_ext/imp_revantage.go @@ -0,0 +1,12 @@ +package openrtb_ext + +// ImpExtRevantage defines the contract for the bidder-specific portion of +// imp.ext when targeting the Revantage adapter. +// +// feedId is required and identifies the publisher feed the impression +// belongs to. placementId and publisherId are optional pass-through values. +type ImpExtRevantage struct { + FeedID string `json:"feedId"` + PlacementID string `json:"placementId,omitempty"` + PublisherID string `json:"publisherId,omitempty"` +} diff --git a/static/bidder-info/revantage.yaml b/static/bidder-info/revantage.yaml new file mode 100644 index 00000000000..034a62817b8 --- /dev/null +++ b/static/bidder-info/revantage.yaml @@ -0,0 +1,22 @@ +endpoint: "https://bid.revantage.io/bid" +geoscope: + - global +maintainer: + email: prebid@revantage.io +modifyingVastXmlAllowed: false +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video +userSync: + iframe: + url: "https://sync.revantage.io/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&r={{.RedirectURL}}" + userMacro: "$UID" + redirect: + url: "https://sync.revantage.io/sync?tag=img&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&r={{.RedirectURL}}" + userMacro: "$UID" diff --git a/static/bidder-params/revantage.json b/static/bidder-params/revantage.json new file mode 100644 index 00000000000..d693afa5714 --- /dev/null +++ b/static/bidder-params/revantage.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Revantage Adapter Params", + "description": "A schema which validates params accepted by the Revantage adapter", + "type": "object", + "properties": { + "feedId": { + "type": "string", + "description": "Revantage feed identifier (required)", + "minLength": 1 + }, + "placementId": { + "type": "string", + "description": "Optional placement identifier" + }, + "publisherId": { + "type": "string", + "description": "Optional publisher identifier" + } + }, + "required": ["feedId"] +} From 9382b490c7bc6e04a3e812361ab7d8ced36e8279 Mon Sep 17 00:00:00 2001 From: v0idxyz <58184010+v0idxyz@users.noreply.github.com> Date: Tue, 28 Apr 2026 12:31:30 +0200 Subject: [PATCH 2/4] updated sync --- static/bidder-info/revantage.yaml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/static/bidder-info/revantage.yaml b/static/bidder-info/revantage.yaml index 034a62817b8..f10fcb323e1 100644 --- a/static/bidder-info/revantage.yaml +++ b/static/bidder-info/revantage.yaml @@ -1,9 +1,7 @@ endpoint: "https://bid.revantage.io/bid" -geoscope: - - global maintainer: - email: prebid@revantage.io -modifyingVastXmlAllowed: false + email: "prebid@revantage.io" +gvlVendorID: 0 capabilities: app: mediaTypes: @@ -14,9 +12,6 @@ capabilities: - banner - video userSync: - iframe: - url: "https://sync.revantage.io/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&r={{.RedirectURL}}" - userMacro: "$UID" redirect: - url: "https://sync.revantage.io/sync?tag=img&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&r={{.RedirectURL}}" - userMacro: "$UID" + url: "https://sync.revantage.io/pbs/usersync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&r={{.RedirectURL}}" + userMacro: "$UID" \ No newline at end of file From 20a555337f340b2e13511c337fe884e8c9f12a84 Mon Sep 17 00:00:00 2001 From: v0idxyz <58184010+v0idxyz@users.noreply.github.com> Date: Tue, 28 Apr 2026 12:45:54 +0200 Subject: [PATCH 3/4] rewrote tests --- adapters/revantage/params_test.go | 2 +- adapters/revantage/revantage.go | 8 ++++---- adapters/revantage/revantage_test.go | 6 +++--- .../revantagetest/exemplary/multi-feed-split.json | 6 ++++-- .../revantagetest/exemplary/multi-imp-same-feed.json | 3 ++- .../revantage/revantagetest/exemplary/simple-banner.json | 3 ++- .../revantage/revantagetest/exemplary/simple-video.json | 3 ++- .../revantagetest/supplemental/204-response.json | 3 ++- .../revantagetest/supplemental/400-response.json | 3 ++- exchange/adapter_builders.go | 2 ++ openrtb_ext/bidders.go | 2 ++ 11 files changed, 26 insertions(+), 15 deletions(-) diff --git a/adapters/revantage/params_test.go b/adapters/revantage/params_test.go index 41cfb6af72c..8a470823f5e 100644 --- a/adapters/revantage/params_test.go +++ b/adapters/revantage/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v4/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/revantage/revantage.go b/adapters/revantage/revantage.go index 532b97c00b8..68fa1b08da6 100644 --- a/adapters/revantage/revantage.go +++ b/adapters/revantage/revantage.go @@ -8,10 +8,10 @@ import ( "strings" "github.com/prebid/openrtb/v20/openrtb2" - "github.com/prebid/prebid-server/v3/adapters" - "github.com/prebid/prebid-server/v3/config" - "github.com/prebid/prebid-server/v3/errortypes" - "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v4/adapters" + "github.com/prebid/prebid-server/v4/config" + "github.com/prebid/prebid-server/v4/errortypes" + "github.com/prebid/prebid-server/v4/openrtb_ext" ) type adapter struct { diff --git a/adapters/revantage/revantage_test.go b/adapters/revantage/revantage_test.go index 2e8bf1ef367..99be3d388ab 100644 --- a/adapters/revantage/revantage_test.go +++ b/adapters/revantage/revantage_test.go @@ -3,9 +3,9 @@ package revantage import ( "testing" - "github.com/prebid/prebid-server/v3/adapters/adapterstest" - "github.com/prebid/prebid-server/v3/config" - "github.com/prebid/prebid-server/v3/openrtb_ext" + "github.com/prebid/prebid-server/v4/adapters/adapterstest" + "github.com/prebid/prebid-server/v4/config" + "github.com/prebid/prebid-server/v4/openrtb_ext" ) func TestJsonSamples(t *testing.T) { diff --git a/adapters/revantage/revantagetest/exemplary/multi-feed-split.json b/adapters/revantage/revantagetest/exemplary/multi-feed-split.json index 567effe816a..778098dc589 100644 --- a/adapters/revantage/revantagetest/exemplary/multi-feed-split.json +++ b/adapters/revantage/revantagetest/exemplary/multi-feed-split.json @@ -33,7 +33,8 @@ "site": {"domain": "example.com"}, "tmax": 1000, "cur": ["USD"] - } + }, + "impIDs": ["imp-a"] }, "mockResponse": { "status": 200, @@ -66,7 +67,8 @@ "site": {"domain": "example.com"}, "tmax": 1000, "cur": ["USD"] - } + }, + "impIDs": ["imp-b"] }, "mockResponse": { "status": 200, diff --git a/adapters/revantage/revantagetest/exemplary/multi-imp-same-feed.json b/adapters/revantage/revantagetest/exemplary/multi-imp-same-feed.json index d25aded04a8..ad7db608a82 100644 --- a/adapters/revantage/revantagetest/exemplary/multi-imp-same-feed.json +++ b/adapters/revantage/revantagetest/exemplary/multi-imp-same-feed.json @@ -38,7 +38,8 @@ "site": {"domain": "example.com"}, "tmax": 1000, "cur": ["USD"] - } + }, + "impIDs": ["imp-1", "imp-2"] }, "mockResponse": { "status": 200, diff --git a/adapters/revantage/revantagetest/exemplary/simple-banner.json b/adapters/revantage/revantagetest/exemplary/simple-banner.json index b9d25666c9a..4afc3a4891d 100644 --- a/adapters/revantage/revantagetest/exemplary/simple-banner.json +++ b/adapters/revantage/revantagetest/exemplary/simple-banner.json @@ -94,7 +94,8 @@ }, "tmax": 1000, "cur": ["USD"] - } + }, + "impIDs": ["imp-1"] }, "mockResponse": { "status": 200, diff --git a/adapters/revantage/revantagetest/exemplary/simple-video.json b/adapters/revantage/revantagetest/exemplary/simple-video.json index 162e838d695..c209fa11075 100644 --- a/adapters/revantage/revantagetest/exemplary/simple-video.json +++ b/adapters/revantage/revantagetest/exemplary/simple-video.json @@ -63,7 +63,8 @@ }, "tmax": 1500, "cur": ["USD"] - } + }, + "impIDs": ["video-imp-1"] }, "mockResponse": { "status": 200, diff --git a/adapters/revantage/revantagetest/supplemental/204-response.json b/adapters/revantage/revantagetest/supplemental/204-response.json index 5da4aa26fe4..a603a4ca484 100644 --- a/adapters/revantage/revantagetest/supplemental/204-response.json +++ b/adapters/revantage/revantagetest/supplemental/204-response.json @@ -28,7 +28,8 @@ "site": {"domain": "example.com"}, "tmax": 1000, "cur": ["USD"] - } + }, + "impIDs": ["imp-1"] }, "mockResponse": { "status": 204, diff --git a/adapters/revantage/revantagetest/supplemental/400-response.json b/adapters/revantage/revantagetest/supplemental/400-response.json index a902fc4ffb9..17846b71972 100644 --- a/adapters/revantage/revantagetest/supplemental/400-response.json +++ b/adapters/revantage/revantagetest/supplemental/400-response.json @@ -28,7 +28,8 @@ "site": {"domain": "example.com"}, "tmax": 1000, "cur": ["USD"] - } + }, + "impIDs": ["imp-1"] }, "mockResponse": { "status": 400, diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 630fbe348b9..7fd4add7731 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -194,6 +194,7 @@ import ( "github.com/prebid/prebid-server/v4/adapters/rediads" "github.com/prebid/prebid-server/v4/adapters/relevantdigital" "github.com/prebid/prebid-server/v4/adapters/resetdigital" + "github.com/prebid/prebid-server/v4/adapters/revantage" "github.com/prebid/prebid-server/v4/adapters/revcontent" "github.com/prebid/prebid-server/v4/adapters/richaudience" "github.com/prebid/prebid-server/v4/adapters/rise" @@ -464,6 +465,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderRediads: rediads.Builder, openrtb_ext.BidderRelevantDigital: relevantdigital.Builder, openrtb_ext.BidderResetDigital: resetdigital.Builder, + openrtb_ext.BidderRevantage: revantage.Builder, openrtb_ext.BidderRevcontent: revcontent.Builder, openrtb_ext.BidderRichaudience: richaudience.Builder, openrtb_ext.BidderRise: rise.Builder, diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 1b61ff724e2..abbdccc4846 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -212,6 +212,7 @@ var coreBidderNames []BidderName = []BidderName{ BidderRediads, BidderRelevantDigital, BidderResetDigital, + BidderRevantage, BidderRevcontent, BidderRichaudience, BidderRise, @@ -586,6 +587,7 @@ const ( BidderRediads BidderName = "rediads" BidderRelevantDigital BidderName = "relevantdigital" BidderResetDigital BidderName = "resetdigital" + BidderRevantage BidderName = "revantage" BidderRevcontent BidderName = "revcontent" BidderRichaudience BidderName = "richaudience" BidderRise BidderName = "rise" From 2937bd53916b4e1083b4b8ab1028b2fa2a0b58c8 Mon Sep 17 00:00:00 2001 From: v0idxyz <58184010+v0idxyz@users.noreply.github.com> Date: Tue, 28 Apr 2026 12:48:56 +0200 Subject: [PATCH 4/4] updated mtype and tests --- adapters/revantage/revantage.go | 33 +++------ .../multi-format-missing-mtype.json | 73 +++++++++++++++++++ analytics/build/testFiles/test-20260428 | 0 3 files changed, 83 insertions(+), 23 deletions(-) create mode 100644 adapters/revantage/revantagetest/supplemental/multi-format-missing-mtype.json create mode 100644 analytics/build/testFiles/test-20260428 diff --git a/adapters/revantage/revantage.go b/adapters/revantage/revantage.go index 68fa1b08da6..4596032e9f5 100644 --- a/adapters/revantage/revantage.go +++ b/adapters/revantage/revantage.go @@ -199,11 +199,15 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, _ *adapters.RequestData return response, errs } -// resolveMediaType picks banner or video based on (in priority order): -// 1. bid.mtype (oRTB 2.6) -// 2. bid.ext.mediaType -// 3. VAST shape detection on bid.adm -// 4. The single configured media type on the originating imp +// resolveMediaType determines the bid's media type. Resolution order: +// 1. bid.mtype (oRTB 2.6) — REQUIRED on bids for multi-format impressions. +// 2. bid.ext.mediaType — legacy signal, accepted as a fallback. +// 3. The single, unambiguous media type on the originating imp (only when the +// imp declares exactly one of banner/video). +// +// Bids on multi-format imps that arrive without bid.mtype or bid.ext.mediaType +// are rejected with a BadServerResponse error. Per Prebid Server guidance, the +// adapter server MUST set MType on every bid; we do not guess. func resolveMediaType(bid *openrtb2.Bid, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { switch bid.MType { case openrtb2.MarkupBanner: @@ -226,10 +230,6 @@ func resolveMediaType(bid *openrtb2.Bid, imps []openrtb2.Imp) (openrtb_ext.BidTy } } - if isVastMarkup(bid.AdM) { - return openrtb_ext.BidTypeVideo, nil - } - for _, imp := range imps { if imp.ID != bid.ImpID { continue @@ -241,24 +241,11 @@ func resolveMediaType(bid *openrtb2.Bid, imps []openrtb2.Imp) (openrtb_ext.BidTy return openrtb_ext.BidTypeVideo, nil case hasBanner && !hasVideo: return openrtb_ext.BidTypeBanner, nil - case hasBanner && hasVideo: - // Multi-format with no explicit signal — default to banner. The Revantage - // endpoint should set mtype or ext.mediaType in this case; this is a fallback. - return openrtb_ext.BidTypeBanner, nil } break } return "", &errortypes.BadServerResponse{ - Message: fmt.Sprintf("could not determine media type for bid %s on imp %s", bid.ID, bid.ImpID), - } -} - -func isVastMarkup(adm string) bool { - trimmed := strings.TrimSpace(adm) - if trimmed == "" { - return false + Message: fmt.Sprintf("could not determine media type for bid %s on imp %s: response missing bid.mtype and bid.ext.mediaType", bid.ID, bid.ImpID), } - upper := strings.ToUpper(trimmed) - return strings.HasPrefix(upper, "ambiguous", + "crid": "c1", + "w": 300, + "h": 250 + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [] + } + ], + "expectedMakeBidsErrors": [ + { + "value": "could not determine media type for bid b1 on imp imp-mf: response missing bid.mtype and bid.ext.mediaType", + "comparison": "literal" + } + ] +} diff --git a/analytics/build/testFiles/test-20260428 b/analytics/build/testFiles/test-20260428 new file mode 100644 index 00000000000..e69de29bb2d