-
Notifications
You must be signed in to change notification settings - Fork 795
New Adapter: Start.io #4324
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: Start.io #4324
Changes from all commits
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,179 @@ | ||
package startio | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"net/url" | ||
"slices" | ||
|
||
"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/jsonutil" | ||
) | ||
|
||
type adapter struct { | ||
endpoint string | ||
} | ||
|
||
func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { | ||
uri, err := url.ParseRequestURI(config.Endpoint) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
bidder := &adapter{ | ||
endpoint: uri.String(), | ||
} | ||
|
||
return bidder, nil | ||
} | ||
|
||
func (adapter *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { | ||
var requests []*adapters.RequestData | ||
var errors []error | ||
requestCopy := *request | ||
|
||
if err := validateRequest(requestCopy); err != nil { | ||
return nil, []error{err} | ||
} | ||
|
||
validImpressions, err := getValidImpressions(requestCopy.Imp) | ||
if err != nil { | ||
return nil, []error{err} | ||
} | ||
for i := range validImpressions { | ||
requestCopy.Imp = []openrtb2.Imp{validImpressions[i]} | ||
|
||
requestBody, err := jsonutil.Marshal(requestCopy) | ||
if err != nil { | ||
errors = append(errors, fmt.Errorf("imp[%d]: failed to marshal request: %w", i, err)) | ||
continue | ||
} | ||
|
||
requestData := &adapters.RequestData{ | ||
Method: http.MethodPost, | ||
Uri: adapter.endpoint, | ||
Body: requestBody, | ||
Headers: buildRequestHeaders(), | ||
ImpIDs: []string{validImpressions[i].ID}, | ||
} | ||
|
||
requests = append(requests, requestData) | ||
} | ||
|
||
return requests, errors | ||
} | ||
|
||
func (adapter *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { | ||
if response.StatusCode == http.StatusNoContent { | ||
return nil, nil | ||
Comment on lines
+71
to
+72
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. Please add a supplemental JSON test called |
||
} else if response.StatusCode != http.StatusOK { | ||
return nil, []error{wrapReqError(fmt.Sprintf("Unexpected status code: %d.", response.StatusCode))} | ||
} | ||
Comment on lines
+73
to
+75
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. Please delete the |
||
|
||
if err := adapters.CheckResponseStatusCodeForErrors(response); err != nil { | ||
return nil, []error{err} | ||
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. Please add a supplemental JSON test called |
||
} | ||
|
||
var bidResponse openrtb2.BidResponse | ||
if err := jsonutil.Unmarshal(response.Body, &bidResponse); err != nil { | ||
return nil, []error{wrapReqError(fmt.Sprintf("failed to unmarshal response body: %v", err))} | ||
} | ||
|
||
var errs []error | ||
bidderResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResponse.SeatBid)) | ||
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. Is it better to start with a capacity derived from |
||
|
||
for i := range bidResponse.SeatBid { | ||
for j := range bidResponse.SeatBid[i].Bid { | ||
bid := &bidResponse.SeatBid[i].Bid[j] | ||
bidType, err := getMediaTypeForBid(*bid) | ||
|
||
if err != nil { | ||
errs = append(errs, err) | ||
} else { | ||
bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{ | ||
Bid: bid, | ||
BidType: bidType, | ||
}) | ||
} | ||
} | ||
} | ||
|
||
return bidderResponse, errs | ||
} | ||
|
||
func validateRequest(request openrtb2.BidRequest) error { | ||
if !isSupportedCurrency(request.Cur) { | ||
return wrapReqError("unsupported currency: only USD is accepted") | ||
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. Please add a supplemental JSON test called |
||
} | ||
|
||
if !hasSiteOrAppID(request) { | ||
return wrapReqError("request must contain either site.id or app.id") | ||
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. Please add supplemental JSON tests called |
||
} | ||
Comment on lines
+113
to
+115
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. Please delete the |
||
|
||
return nil | ||
} | ||
|
||
func getValidImpressions(imps []openrtb2.Imp) ([]openrtb2.Imp, error) { | ||
var validImpressions []openrtb2.Imp | ||
|
||
for _, imp := range imps { | ||
imp.Audio = nil | ||
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. You can delete this line. There's no need to explicitly delete audio. PBS core will strip out any media type objects that your bidder has not declared support for in your YAML file. |
||
hasValidMedia := imp.Banner != nil || imp.Video != nil || imp.Native != nil | ||
if hasValidMedia { | ||
validImpressions = append(validImpressions, imp) | ||
} | ||
} | ||
|
||
if len(validImpressions) == 0 { | ||
return nil, wrapReqError("invalid bid request: at least one banner, video, or native impression is required.") | ||
} | ||
|
||
return validImpressions, nil | ||
} | ||
Comment on lines
+120
to
+136
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. Please delete this function as it is unnecessary. I see that you're performing validation checks to ensure the request contains a media type that you support and also that there is at least one impression. PBS core will only ever call your adapter if there is a valid media type and at least one valid impression. |
||
|
||
func hasSiteOrAppID(req openrtb2.BidRequest) bool { | ||
return (req.Site != nil && req.Site.ID != "") || (req.App != nil && req.App.ID != "") | ||
} | ||
Comment on lines
+138
to
+140
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. I just want to point that there is already some site and app validation being performed by PBS core. Please see |
||
|
||
func buildRequestHeaders() http.Header { | ||
headers := http.Header{} | ||
headers.Add("Content-Type", "application/json;charset=utf-8") | ||
headers.Add("Accept", "application/json") | ||
headers.Add("X-Openrtb-Version", "2.5") | ||
|
||
return headers | ||
} | ||
|
||
func isSupportedCurrency(currencies []string) bool { | ||
return len(currencies) == 0 || slices.Contains(currencies, "USD") | ||
} | ||
|
||
func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { | ||
|
||
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. Super nitpick: delete blank line |
||
if bid.Ext != nil { | ||
var bidExt openrtb_ext.ExtBid | ||
err := jsonutil.Unmarshal(bid.Ext, &bidExt) | ||
if err == nil && bidExt.Prebid != nil { | ||
switch bidExt.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 | ||
} | ||
} | ||
} | ||
|
||
return "", &errortypes.BadServerResponse{ | ||
Message: fmt.Sprintf("Failed to parse bid media type for impression %s.", bid.ImpID), | ||
} | ||
Comment on lines
+172
to
+174
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. Please add a supplemental JSON test called |
||
} | ||
|
||
func wrapReqError(errorStr string) *errortypes.BadInput { | ||
return &errortypes.BadInput{Message: errorStr} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this is not in the documentation yet but please use the adapter utility function
adapters#IsResponseStatusCodeNoContent
to check for status 204: