-
Notifications
You must be signed in to change notification settings - Fork 867
New Adapter: Pixfuture #4117
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
base: master
Are you sure you want to change the base?
New Adapter: Pixfuture #4117
Changes from all commits
93e6573
458fad1
d67a079
a9fe81b
606b185
061b818
d0bc2e1
eec17ff
cb8e3a7
bba6eed
22df0f8
3f33089
a22b19e
993f157
284c847
9e470a0
3b4ca26
1df8f71
6a59158
2ba1a64
ea7d94e
8dac147
078c276
0b62c21
7bdabf4
3f219c9
bb9b65d
7a3352f
3f13c25
a407b22
3457b8c
f1b859c
025a574
eece8b6
33fbff5
1912ebd
81c11fb
817bebf
2f15c50
c5b5f20
be83555
f8fddb7
1aa2761
90328f3
3a1d57b
14278ca
7a44e1c
d78a147
3beb6bc
27148f5
fd9504e
a3e6d9a
aa3b6b2
d3c660b
edbb31c
0600cda
986bcf7
89fb585
77157f2
9cf8565
c21c807
0b1b509
e8b0591
461d626
351b275
2b9a50f
d5dd656
5d3bef4
c1f03ba
0a39bde
857c4c1
89ae5f6
bac0bf5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| package pixfuture | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "testing" | ||
|
|
||
| "github.com/prebid/prebid-server/v3/openrtb_ext" | ||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func TestValidParams(t *testing.T) { | ||
| validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") | ||
| require.NoError(t, err, "Failed to fetch the json schema.") | ||
|
|
||
| tests := []struct { | ||
| name string | ||
| json string | ||
| }{ | ||
| { | ||
| name: "Minimum length satisfied", | ||
| json: `{"pix_id": "123"}`, | ||
| }, | ||
| { | ||
| name: "Longer valid string", | ||
| json: `{"pix_id": "abcdef"}`, | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| tt := tt // capture range variable | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nitpick (non-blocking): This hasn't been needed for several versions of go now Why
|
||
| t.Run(tt.name, func(t *testing.T) { | ||
| assert.NoErrorf(t, validator.Validate(openrtb_ext.BidderPixfuture, json.RawMessage(tt.json)), "Schema rejected valid params: %s", tt.json) | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestInvalidParams(t *testing.T) { | ||
pixfuture-media marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") | ||
| require.NoError(t, err, "Failed to fetch the json schema.") | ||
|
|
||
| tests := []struct { | ||
| name string | ||
| json string | ||
| }{ | ||
| { | ||
| name: "Wrong type (integer)", | ||
| json: `{"pix_id": 123}`, | ||
| }, | ||
| { | ||
| name: "Too short (minLength: 3)", | ||
| json: `{"pix_id": "ab"}`, | ||
| }, | ||
| { | ||
| name: "Missing required pix_id", | ||
| json: `{}`, | ||
| }, | ||
| { | ||
| name: "Empty string (violates minLength)", | ||
| json: `{"pix_id": ""}`, | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| tt := tt // capture range variable | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| err := validator.Validate(openrtb_ext.BidderPixfuture, json.RawMessage(tt.json)) | ||
| assert.Errorf(t, err, "Schema allowed invalid params: %s", tt.json) | ||
| }) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,175 @@ | ||
| // Package pixfuture implements the Pixfuture adapter for Prebid Server. | ||
| // It provides functionality to handle bid requests and responses according to Pixfuture's specifications | ||
| package pixfuture | ||
pixfuture-media marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| import ( | ||
| "fmt" | ||
| "net/http" | ||
|
|
||
| "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/v3/util/iterutil" | ||
| "github.com/prebid/prebid-server/v3/util/jsonutil" | ||
| ) | ||
|
|
||
| type adapter struct { | ||
| endpoint string | ||
| } | ||
|
|
||
| func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { | ||
| return &adapter{ | ||
| endpoint: config.Endpoint, | ||
| }, nil | ||
| } | ||
|
|
||
| func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { | ||
|
|
||
| var errs []error | ||
| var adapterRequests []*adapters.RequestData | ||
| headers := http.Header{} | ||
| headers.Set("Content-Type", "application/json") | ||
| headers.Set("Accept", "application/json") | ||
|
|
||
| for imp := range iterutil.SlicePointerValues(request.Imp) { | ||
|
|
||
| var bidderExt adapters.ExtImpBidder | ||
| var pixfutureExt openrtb_ext.ImpExtPixfuture | ||
|
|
||
| if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { | ||
pixfuture-media marked this conversation as resolved.
Show resolved
Hide resolved
pixfuture-media marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| errs = append(errs, err) | ||
| continue | ||
| } | ||
| if err := jsonutil.Unmarshal(bidderExt.Bidder, &pixfutureExt); err != nil { | ||
| errs = append(errs, err) | ||
| continue | ||
| } | ||
| if pixfutureExt.PixID == "" { | ||
| errs = append(errs, &errortypes.BadInput{Message: "Missing required parameter pix_id"}) | ||
| continue | ||
| } | ||
|
|
||
| requestCopy := *request | ||
| requestCopy.Imp = []openrtb2.Imp{*imp} // slice notation with dereferencing | ||
|
|
||
| reqJSON, err := jsonutil.Marshal(requestCopy) | ||
| if err != nil { | ||
| errs = append(errs, err) | ||
| continue | ||
| } | ||
|
|
||
| adapterRequests = append(adapterRequests, &adapters.RequestData{ | ||
| Method: http.MethodPost, | ||
| Uri: a.endpoint, | ||
| Body: reqJSON, | ||
| Headers: headers, | ||
| ImpIDs: []string{imp.ID}, | ||
| }) | ||
| } | ||
|
|
||
| if len(adapterRequests) == 0 && len(errs) > 0 { | ||
| return nil, errs | ||
| } | ||
|
Comment on lines
+72
to
+74
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Delete this conditional block as this case is handled in the return statement such that if there are no adapterRequests, |
||
| return adapterRequests, errs | ||
| } | ||
|
|
||
| func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { | ||
| if adapters.IsResponseStatusCodeNoContent(response) { | ||
| return nil, nil | ||
| } | ||
|
|
||
| if err := adapters.CheckResponseStatusCodeForErrors(response); err != nil { | ||
| return nil, []error{err} | ||
| } | ||
|
|
||
| var bidResp openrtb2.BidResponse | ||
| // Step 1: Check if body is empty to avoid unnecessary unmarshal overhead | ||
| if len(response.Body) == 0 { | ||
| return nil, nil // Or return a specific error if Pixfuture always expects a body | ||
| } | ||
|
|
||
| // Step 2: The Unmarshal call (Lines 88-92) | ||
| if err := jsonutil.Unmarshal(response.Body, &bidResp); err != nil { | ||
| return nil, []error{&errortypes.BadServerResponse{ | ||
| Message: fmt.Sprintf("Invalid response format: %s", err.Error()), | ||
| }} | ||
| } | ||
pixfuture-media marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Pre-calculate total number of bids | ||
| expectedBids := 0 | ||
| for i := range bidResp.SeatBid { | ||
| expectedBids += len(bidResp.SeatBid[i].Bid) | ||
| } | ||
|
|
||
| bidResponse := adapters.NewBidderResponseWithBidsCapacity(expectedBids) | ||
| bidResponse.Currency = bidResp.Cur | ||
|
|
||
| var errs []error | ||
| for i := range bidResp.SeatBid { | ||
| // Use index j to avoid copying the large openrtb2.Bid struct | ||
| for j := range bidResp.SeatBid[i].Bid { | ||
| bid := &bidResp.SeatBid[i].Bid[j] | ||
|
|
||
| bidType, err := getMediaTypeForBid(bid) | ||
| if err != nil { | ||
| errs = append(errs, &errortypes.BadServerResponse{ | ||
| Message: "Failed to parse impression \"" + bid.ImpID + "\" mediatype", | ||
| }) | ||
| continue | ||
| } | ||
|
|
||
| bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ | ||
| Bid: bid, | ||
| BidType: bidType, | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| if len(bidResponse.Bids) == 0 { | ||
| return nil, errs | ||
| } | ||
|
|
||
| return bidResponse, errs | ||
| } | ||
|
|
||
| func getMediaTypeForBid(bid *openrtb2.Bid) (openrtb_ext.BidType, error) { | ||
| // First try standard MType field (OpenRTB 2.6) | ||
| 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 | ||
| } | ||
|
|
||
| // Fallback to custom extension | ||
| var ext struct { | ||
| Prebid struct { | ||
| Type string `json:"type"` | ||
| } `json:"prebid"` | ||
pixfuture-media marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
Comment on lines
+149
to
+153
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can use the ext definition in
Comment on lines
+149
to
+153
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As mentioned here, you should replace this with the defintion in |
||
|
|
||
| if len(bid.Ext) > 0 { | ||
| if err := jsonutil.Unmarshal(bid.Ext, &ext); err != nil { | ||
| return "", &errortypes.BadServerResponse{ | ||
| Message: fmt.Sprintf("Failed to parse bid ext for impression %s: %v", bid.ImpID, err), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| switch ext.Prebid.Type { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider this as a suggestion. The current implementation follows an anti-pattern, assumes that if there is a multi-format request, the media type defaults to openrtb_ext.BidTypeBanner. Prebid server expects the media type to be explicitly set in the adapter response. Therefore, we strongly recommend implementing a pattern where the adapter server sets the MType field in the response to accurately determine the media type for the impression. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider this as a suggestion. The current implementation follows an anti-pattern, assumes that if there is a multi-format request, the media type defaults to openrtb_ext.BidTypeNative. Prebid server expects the media type to be explicitly set in the adapter response. Therefore, we strongly recommend implementing a pattern where the adapter server sets the MType field in the response to accurately determine the media type for the impression. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider this as a suggestion. The current implementation follows an anti-pattern, assumes that if there is a multi-format request, the media type defaults to openrtb_ext.BidTypeVideo. Prebid server expects the media type to be explicitly set in the adapter response. Therefore, we strongly recommend implementing a pattern where the adapter server sets the MType field in the response to accurately determine the media type for the impression. |
||
| case "banner": | ||
| return openrtb_ext.BidTypeBanner, nil | ||
| case "video": | ||
| return openrtb_ext.BidTypeVideo, nil | ||
| case "native": | ||
| return openrtb_ext.BidTypeNative, nil | ||
pixfuture-media marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| default: | ||
| return "", &errortypes.BadServerResponse{ | ||
| Message: fmt.Sprintf("Unknown bid type for impression %s", bid.ImpID), | ||
| } | ||
pixfuture-media marked this conversation as resolved.
Show resolved
Hide resolved
pixfuture-media marked this conversation as resolved.
Show resolved
Hide resolved
pixfuture-media marked this conversation as resolved.
Show resolved
Hide resolved
pixfuture-media marked this conversation as resolved.
Show resolved
Hide resolved
pixfuture-media marked this conversation as resolved.
Show resolved
Hide resolved
pixfuture-media marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unit tests are considered nice to haves. The JSON test framework is required to achieve code coverage wherever possible. Please see the developer docs Test Your Adapter section on how to test your adapter.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test JSONs have been reviewed and edited.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test stuff has been reviewed and recreated
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. None of your JSON tests are being executed because you're not calling |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package pixfuture | ||
|
|
||
| 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/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func TestJsonSamples(t *testing.T) { | ||
| bidder, buildErr := Builder(openrtb_ext.BidderPixfuture, config.Adapter{ | ||
| Endpoint: "http://any.url", | ||
| }, config.Server{}) | ||
| require.NoError(t, buildErr, "Builder returned unexpected error") | ||
|
|
||
| adapterstest.RunJSONBidderTest(t, "pixfuturetest", bidder) | ||
| } | ||
pixfuture-media marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| { | ||
| "mockBidRequest": { | ||
| "id": "test-request-id", | ||
| "site": { | ||
| "page": "test.com", | ||
| "publisher": { | ||
| "id": "123456789" | ||
| } | ||
| }, | ||
| "imp": [ | ||
| { | ||
| "id": "imp1", | ||
| "banner": { | ||
| "w": 300, | ||
| "h": 250 | ||
| }, | ||
| "ext": { | ||
| "bidder": { | ||
| "pix_id": "55463" | ||
| } | ||
| } | ||
| } | ||
| ] | ||
| }, | ||
| "httpCalls": [ | ||
| { | ||
| "expectedRequest": { | ||
| "uri": "http://any.url", | ||
| "headers": { | ||
| "Content-Type": [ | ||
| "application/json" | ||
| ], | ||
| "Accept": [ | ||
| "application/json" | ||
| ] | ||
| }, | ||
| "body": { | ||
| "id": "test-request-id", | ||
| "site": { | ||
| "page": "test.com", | ||
| "publisher": { | ||
| "id": "123456789" | ||
| } | ||
| }, | ||
| "imp": [ | ||
| { | ||
| "id": "imp1", | ||
| "banner": { | ||
| "w": 300, | ||
| "h": 250 | ||
| }, | ||
| "ext": { | ||
| "bidder": { | ||
| "pix_id": "55463" | ||
| } | ||
| } | ||
| } | ||
| ] | ||
| }, | ||
| "impIDs": [ | ||
| "imp1" | ||
| ] | ||
| }, | ||
| "mockResponse": { | ||
| "status": 200, | ||
| "body": { | ||
| "id": "test-response-id", | ||
| "cur": "USD", | ||
| "seatbid": [ | ||
| { | ||
| "bid": [ | ||
| { | ||
| "id": "bid1", | ||
| "impid": "imp1", | ||
| "price": 1.23, | ||
| "adm": "<div>Banner Ad</div>", | ||
| "ext": { | ||
| "prebid": { | ||
| "type": "banner" | ||
| } | ||
| } | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| } | ||
| ], | ||
| "expectedBidResponses": [ | ||
| { | ||
| "bids": [ | ||
| { | ||
| "bid": { | ||
| "id": "bid1", | ||
| "impid": "imp1", | ||
| "price": 1.23, | ||
| "adm": "<div>Banner Ad</div>", | ||
| "ext": { | ||
| "prebid": { | ||
| "type": "banner" | ||
| } | ||
| } | ||
| }, | ||
| "type": "banner" | ||
| } | ||
| ], | ||
| "currency": "USD" | ||
| } | ||
| ] | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.