-
Notifications
You must be signed in to change notification settings - Fork 928
New Adapter: BidWave #4804
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
New Adapter: BidWave #4804
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,264 @@ | ||
| package bidwave | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "fmt" | ||
| "net/http" | ||
| "regexp" | ||
| "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" | ||
| "github.com/prebid/prebid-server/v4/util/jsonutil" | ||
| ) | ||
|
|
||
| const defaultCurrency = "USD" | ||
|
|
||
| var uuidRegex = regexp.MustCompile(`(?i)^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`) | ||
|
|
||
| type adapter struct { | ||
| endpoint string | ||
| } | ||
|
|
||
| type requestExtBidwave struct { | ||
| PID string `json:"pid"` | ||
| } | ||
|
|
||
| type impGroup struct { | ||
| publisherID string | ||
| imps []openrtb2.Imp | ||
| } | ||
|
|
||
| func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) { | ||
| return &adapter{ | ||
| endpoint: config.Endpoint, | ||
| }, nil | ||
| } | ||
|
|
||
| func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { | ||
| groups, errs := groupImpsByPublisherID(request.Imp, reqInfo) | ||
|
|
||
| requests := make([]*adapters.RequestData, 0, len(groups)) | ||
| for _, group := range groups { | ||
| outgoingRequest := *request | ||
| outgoingRequest.Cur = []string{defaultCurrency} | ||
| outgoingRequest.Imp = group.imps | ||
|
|
||
| ext, err := setBidwaveExt(outgoingRequest.Ext, group.publisherID) | ||
| if err != nil { | ||
| errs = append(errs, err) | ||
| continue | ||
| } | ||
| outgoingRequest.Ext = ext | ||
|
|
||
| requestJSON, err := jsonutil.Marshal(outgoingRequest) | ||
| if err != nil { | ||
| errs = append(errs, err) | ||
| continue | ||
| } | ||
|
|
||
| headers := http.Header{} | ||
| headers.Add("Content-Type", "application/json;charset=utf-8") | ||
|
|
||
| requests = append(requests, &adapters.RequestData{ | ||
| Method: http.MethodPost, | ||
| Uri: a.endpoint, | ||
| Body: requestJSON, | ||
| Headers: headers, | ||
| ImpIDs: openrtb_ext.GetImpIDs(group.imps), | ||
| }) | ||
| } | ||
|
|
||
| return requests, errs | ||
| } | ||
|
|
||
| func prepareImpCurrency(imp openrtb2.Imp, reqInfo *adapters.ExtraRequestInfo) (openrtb2.Imp, error) { | ||
| if imp.BidFloor > 0 && strings.TrimSpace(imp.BidFloorCur) != "" && !strings.EqualFold(imp.BidFloorCur, defaultCurrency) { | ||
| bidFloor, err := reqInfo.ConvertCurrency(imp.BidFloor, imp.BidFloorCur, defaultCurrency) | ||
| if err != nil { | ||
| return imp, &errortypes.BadInput{ | ||
| Message: fmt.Sprintf("expected currency %s for bid floor; unable to convert from %s for impression %s: %s", defaultCurrency, imp.BidFloorCur, imp.ID, err.Error()), | ||
| } | ||
| } | ||
|
|
||
| imp.BidFloor = bidFloor | ||
| imp.BidFloorCur = defaultCurrency | ||
| } | ||
|
|
||
| return imp, nil | ||
| } | ||
|
|
||
| func groupImpsByPublisherID(imps []openrtb2.Imp, reqInfo *adapters.ExtraRequestInfo) ([]impGroup, []error) { | ||
| groupIndexes := make(map[string]int) | ||
| groups := make([]impGroup, 0, len(imps)) | ||
| errs := make([]error, 0) | ||
|
|
||
| for i := range imps { | ||
| publisherID, err := parsePublisherID(imps[i]) | ||
| if err != nil { | ||
| errs = append(errs, err) | ||
| continue | ||
| } | ||
|
|
||
| imp, err := prepareImpCurrency(imps[i], reqInfo) | ||
| if err != nil { | ||
| errs = append(errs, err) | ||
| continue | ||
| } | ||
|
|
||
| if groupIndex, ok := groupIndexes[publisherID]; ok { | ||
| groups[groupIndex].imps = append(groups[groupIndex].imps, imp) | ||
| continue | ||
| } | ||
|
|
||
| groupIndexes[publisherID] = len(groups) | ||
| groups = append(groups, impGroup{ | ||
| publisherID: publisherID, | ||
| imps: []openrtb2.Imp{imp}, | ||
| }) | ||
| } | ||
|
|
||
| return groups, errs | ||
| } | ||
|
|
||
| func parsePublisherID(imp openrtb2.Imp) (string, error) { | ||
| var ext adapters.ExtImpBidder | ||
| if err := jsonutil.Unmarshal(imp.Ext, &ext); err != nil { | ||
| return "", &errortypes.BadInput{ | ||
| Message: fmt.Sprintf("invalid imp.ext for impression %s: %s", imp.ID, err.Error()), | ||
| } | ||
| } | ||
|
|
||
| var params openrtb_ext.ExtImpBidwave | ||
| if err := jsonutil.Unmarshal(ext.Bidder, ¶ms); err != nil { | ||
| return "", &errortypes.BadInput{ | ||
| Message: fmt.Sprintf("invalid bidwave params for impression %s: %s", imp.ID, err.Error()), | ||
| } | ||
| } | ||
|
|
||
| if !uuidRegex.MatchString(params.PublisherID) { | ||
| return "", &errortypes.BadInput{ | ||
| Message: fmt.Sprintf("invalid publisherId for impression %s", imp.ID), | ||
| } | ||
| } | ||
|
|
||
| return params.PublisherID, nil | ||
| } | ||
|
|
||
| func setBidwaveExt(rawExt json.RawMessage, publisherID string) (json.RawMessage, error) { | ||
| ext := map[string]json.RawMessage{} | ||
| if len(rawExt) > 0 { | ||
| if err := jsonutil.Unmarshal(rawExt, &ext); err != nil { | ||
| return nil, err | ||
| } | ||
| } | ||
|
|
||
| bidwaveExt, err := jsonutil.Marshal(requestExtBidwave{PID: publisherID}) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| ext["bidwave"] = bidwaveExt | ||
| return jsonutil.Marshal(ext) | ||
| } | ||
|
|
||
| func (a *adapter) MakeBids(request *openrtb2.BidRequest, _ *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 bidResponse openrtb2.BidResponse | ||
| if err := jsonutil.Unmarshal(response.Body, &bidResponse); err != nil { | ||
| return nil, []error{&errortypes.BadServerResponse{ | ||
| Message: "Bad Server Response", | ||
| }} | ||
| } | ||
|
|
||
| result := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) | ||
| if bidResponse.Cur != "" { | ||
| result.Currency = bidResponse.Cur | ||
| } | ||
|
|
||
| var errs []error | ||
| for seatBidIndex := range bidResponse.SeatBid { | ||
| for bidIndex := range bidResponse.SeatBid[seatBidIndex].Bid { | ||
| bid := &bidResponse.SeatBid[seatBidIndex].Bid[bidIndex] | ||
| bidType, err := getBidType(bid, request.Imp) | ||
| if err != nil { | ||
| errs = append(errs, err) | ||
| continue | ||
| } | ||
|
|
||
| result.Bids = append(result.Bids, &adapters.TypedBid{ | ||
| Bid: bid, | ||
| BidType: bidType, | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| return result, errs | ||
| } | ||
|
|
||
| func getBidType(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 | ||
| case 0: | ||
| return getBidTypeFromImp(bid.ImpID, imps) | ||
| default: | ||
| return "", &errortypes.BadServerResponse{ | ||
| Message: fmt.Sprintf("Unsupported MType %d for impression with ID: \"%s\"", bid.MType, bid.ImpID), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func getBidTypeFromImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { | ||
| for _, imp := range imps { | ||
| if imp.ID == impID { | ||
| if isMultiFormatImp(imp) { | ||
| return "", &errortypes.BadServerResponse{ | ||
| Message: fmt.Sprintf("Bid must have non-zero MType for multi format impression with ID: \"%s\"", impID), | ||
| } | ||
| } | ||
| if imp.Banner != 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. 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, nil. 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. |
||
| return openrtb_ext.BidTypeBanner, nil | ||
| } | ||
| if imp.Video != nil { | ||
|
eng-bidwave marked this conversation as resolved.
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, nil. 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, nil. 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. |
||
| return openrtb_ext.BidTypeVideo, nil | ||
| } | ||
| return "", &errortypes.BadServerResponse{ | ||
| Message: fmt.Sprintf("Could not determine MType from impression with ID: \"%s\"", impID), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return "", &errortypes.BadServerResponse{ | ||
| Message: fmt.Sprintf("Failed to find impression for ID: \"%s\"", impID), | ||
| } | ||
| } | ||
|
|
||
| func isMultiFormatImp(imp openrtb2.Imp) bool { | ||
| formatCount := 0 | ||
| if imp.Banner != nil { | ||
| formatCount++ | ||
| } | ||
| if imp.Video != nil { | ||
| formatCount++ | ||
| } | ||
| if imp.Audio != nil { | ||
| formatCount++ | ||
| } | ||
| if imp.Native != nil { | ||
| formatCount++ | ||
| } | ||
| return formatCount > 1 | ||
| } | ||
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.
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, nil. 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.