-
Notifications
You must be signed in to change notification settings - Fork 897
New Adapter: ReVantage #4756
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
Open
v0idxyz
wants to merge
5
commits into
prebid:master
Choose a base branch
from
v0idxyz:master
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
New Adapter: ReVantage #4756
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| package revantage | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "testing" | ||
|
|
||
| "github.com/prebid/prebid-server/v4/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}`, | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,251 @@ | ||
| package revantage | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "fmt" | ||
| "net/http" | ||
| "net/url" | ||
| "strings" | ||
|
|
||
| "github.com/prebid/openrtb/v20/openrtb2" | ||
| "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 { | ||
| 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 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: | ||
| 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) { | ||
|
v0idxyz marked this conversation as resolved.
|
||
| case "banner": | ||
| return openrtb_ext.BidTypeBanner, nil | ||
| case "video": | ||
| 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 | ||
| } | ||
| break | ||
| } | ||
|
|
||
| return "", &errortypes.BadServerResponse{ | ||
| 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), | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package revantage | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "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) { | ||
| bidder, buildErr := Builder(openrtb_ext.BidderRevantage, config.Adapter{ | ||
| Endpoint: "https://bid.revantage.io/bid", | ||
| }, config.Server{ | ||
| ExternalUrl: "http://hosturl.com", | ||
| GvlID: 1, | ||
| DataCenter: "2", | ||
| }) | ||
|
|
||
| if buildErr != nil { | ||
| t.Fatalf("Builder returned unexpected error: %v", buildErr) | ||
| } | ||
|
|
||
| adapterstest.RunJSONBidderTest(t, "revantagetest", bidder) | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.