-
Notifications
You must be signed in to change notification settings - Fork 946
New Adapter: StackAdapt #4724
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: StackAdapt #4724
Changes from 19 commits
2e8133c
b4d9dfd
31ef553
eddb6ea
b6cf5d7
cc99357
b7e9b81
8034cd6
614b5c0
d381023
3af52dd
c995966
fc88f23
abb600c
df84272
f40fb9f
9d9cd21
5b66f22
1703abf
b305f21
79e6c67
9d493da
2393fee
4cda345
ccd4ad3
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,58 @@ | ||
| package stackadapt | ||
|
|
||
| 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.BidderStackAdapt, json.RawMessage(validParam)); err != nil { | ||
| t.Errorf("Schema rejected stackadapt params: %s", validParam) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| 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.BidderStackAdapt, json.RawMessage(invalidParam)); err == nil { | ||
| t.Errorf("Schema allowed unexpected params: %s", invalidParam) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| var validParams = []string{ | ||
| `{"publisherId":"pub-123","supplyId":"ssp-1"}`, | ||
| `{"publisherId":"pub-123","supplyId":"ssp-1","placementId":"placement-456"}`, | ||
| `{"publisherId":"pub-123","supplyId":"ssp-1","banner":{"expdir":[1,3]}}`, | ||
| `{"publisherId":"pub-123","supplyId":"ssp-1","bidfloor":1.5}`, | ||
| `{"publisherId":"pub-123","supplyId":"ssp-1","placementId":"pl-1","banner":{"expdir":[1,2,3]},"bidfloor":0.5}`, | ||
| } | ||
|
|
||
| var invalidParams = []string{ | ||
| ``, | ||
| `null`, | ||
| `true`, | ||
| `5`, | ||
| `[]`, | ||
| `{}`, | ||
| `{"placementId":"placement-456"}`, | ||
| `{"publisherId":"pub-123"}`, | ||
| `{"supplyId":"ssp-1"}`, | ||
| `{"publisherId":""}`, | ||
| `{"publisherId":"pub-123","supplyId":""}`, | ||
| `{"publisherId":"pub-123","supplyId":"ssp-1","bidfloor":-1}`, | ||
| `{"publisherId":"pub-123","supplyId":"ssp-1","banner":"invalid"}`, | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,227 @@ | ||
| package stackadapt | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "errors" | ||
| "fmt" | ||
| "net/http" | ||
| "strconv" | ||
| "strings" | ||
| "text/template" | ||
|
|
||
| "github.com/buger/jsonparser" | ||
| "github.com/prebid/openrtb/v20/adcom1" | ||
| "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/macros" | ||
| "github.com/prebid/prebid-server/v4/openrtb_ext" | ||
| "github.com/prebid/prebid-server/v4/util/jsonutil" | ||
| ) | ||
|
|
||
| type adapter struct { | ||
| endpoint *template.Template | ||
| } | ||
|
|
||
| func Builder(_ openrtb_ext.BidderName, cfg config.Adapter, _ config.Server) (adapters.Bidder, error) { | ||
| endpointTemplate, err := template.New("endpointTemplate").Parse(cfg.Endpoint) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("unable to parse endpoint url template: %w", err) | ||
| } | ||
| return &adapter{ | ||
| endpoint: endpointTemplate, | ||
| }, nil | ||
| } | ||
|
|
||
| func (a *adapter) MakeRequests(request *openrtb2.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { | ||
| // Imp level | ||
| publisherID, supplyID, err := setImpsAndGetEndpointParams(request) | ||
| if err != nil { | ||
| return nil, []error{err} | ||
| } | ||
|
|
||
| // Request level | ||
| setPublisherID(request, publisherID) | ||
|
|
||
| endpointURL, err := a.buildEndpointURL(publisherID, supplyID) | ||
| if err != nil { | ||
| return nil, []error{err} | ||
| } | ||
|
|
||
| body, err := json.Marshal(request) | ||
| if err != nil { | ||
| return nil, []error{fmt.Errorf("marshal bidRequest: %w", err)} | ||
| } | ||
|
|
||
| return []*adapters.RequestData{{ | ||
| Method: http.MethodPost, | ||
| Uri: endpointURL, | ||
| Body: body, | ||
| Headers: http.Header{ | ||
| "Content-Type": {"application/json;charset=utf-8"}, | ||
| "Accept": {"application/json"}, | ||
| }, | ||
| ImpIDs: openrtb_ext.GetImpIDs(request.Imp), | ||
| }}, nil | ||
| } | ||
|
|
||
| func (a *adapter) buildEndpointURL(publisherID, supplyID string) (string, error) { | ||
| params := macros.EndpointTemplateParams{ | ||
| PublisherID: publisherID, | ||
| SupplyId: supplyID, | ||
| } | ||
| return macros.ResolveMacros(a.endpoint, params) | ||
| } | ||
|
|
||
| func setImpsAndGetEndpointParams(request *openrtb2.BidRequest) (string, string, error) { | ||
| var publisherID, supplyID string | ||
| for i, imp := range request.Imp { | ||
| var bidderExt adapters.ExtImpBidder | ||
| if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil { | ||
| return "", "", &errortypes.BadInput{Message: fmt.Sprintf("imp[%d]: unable to unmarshal ext: %s", i, err.Error())} | ||
| } | ||
|
|
||
| var saExt openrtb_ext.ExtImpStackAdapt | ||
| if err := jsonutil.Unmarshal(bidderExt.Bidder, &saExt); err != nil { | ||
| return "", "", &errortypes.BadInput{Message: fmt.Sprintf("imp[%d]: unable to unmarshal bidder ext: %s", i, err.Error())} | ||
| } | ||
|
|
||
| publisherID = saExt.PublisherId | ||
| supplyID = saExt.SupplyId | ||
|
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. Multi-imp:
Contributor
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. Thanks for flagging, revised to validate all |
||
|
|
||
| if saExt.PlacementId != nil { | ||
| request.Imp[i].TagID = *saExt.PlacementId | ||
| } | ||
|
|
||
| if saExt.BidFloor > 0 { | ||
| request.Imp[i].BidFloor = saExt.BidFloor | ||
| request.Imp[i].BidFloorCur = "USD" | ||
| } | ||
|
|
||
| if saExt.Banner != nil && len(saExt.Banner.ExpDir) > 0 && request.Imp[i].Banner != nil { | ||
| bannerCopy := *request.Imp[i].Banner | ||
| bannerCopy.ExpDir = make([]adcom1.ExpandableDirection, len(saExt.Banner.ExpDir)) | ||
| for j, dir := range saExt.Banner.ExpDir { | ||
| bannerCopy.ExpDir[j] = adcom1.ExpandableDirection(dir) | ||
| } | ||
| request.Imp[i].Banner = &bannerCopy | ||
| } | ||
| } | ||
|
|
||
| return publisherID, supplyID, nil | ||
| } | ||
|
|
||
| func setPublisherID(request *openrtb2.BidRequest, publisherID string) { | ||
| if request.Site != nil { | ||
| siteCopy := *request.Site | ||
| if siteCopy.Publisher != nil { | ||
| publisherCopy := *siteCopy.Publisher | ||
| publisherCopy.ID = publisherID | ||
| siteCopy.Publisher = &publisherCopy | ||
|
franklong-sa marked this conversation as resolved.
|
||
| } else { | ||
| siteCopy.Publisher = &openrtb2.Publisher{ID: publisherID} | ||
| } | ||
| request.Site = &siteCopy | ||
| } else if request.App != nil { | ||
| appCopy := *request.App | ||
| if appCopy.Publisher != nil { | ||
| publisherCopy := *appCopy.Publisher | ||
| publisherCopy.ID = publisherID | ||
| appCopy.Publisher = &publisherCopy | ||
|
franklong-sa marked this conversation as resolved.
|
||
| } else { | ||
| appCopy.Publisher = &openrtb2.Publisher{ID: publisherID} | ||
| } | ||
| request.App = &appCopy | ||
| } | ||
| } | ||
|
|
||
| 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} | ||
|
franklong-sa marked this conversation as resolved.
|
||
| } | ||
|
|
||
| var response openrtb2.BidResponse | ||
| if err := jsonutil.Unmarshal(responseData.Body, &response); err != nil { | ||
| return nil, []error{err} | ||
| } | ||
|
|
||
| bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) | ||
| bidResponse.Currency = response.Cur | ||
|
|
||
| var errs []error | ||
| for _, seatBid := range response.SeatBid { | ||
| for i := range seatBid.Bid { | ||
| bidType, err := getMediaTypeForBid(seatBid.Bid[i]) | ||
| if err != nil { | ||
| errs = append(errs, err) | ||
| continue | ||
| } | ||
|
|
||
| if bidType == openrtb_ext.BidTypeNative { | ||
| seatBid.Bid[i].AdM, err = getNativeAdm(seatBid.Bid[i].AdM) | ||
| if err != nil { | ||
| errs = append(errs, err) | ||
| continue | ||
| } | ||
| } | ||
| resolveMacros(&seatBid.Bid[i]) | ||
|
|
||
| bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ | ||
| Bid: &seatBid.Bid[i], | ||
| BidType: bidType, | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| return bidResponse, errs | ||
| } | ||
|
|
||
| func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { | ||
| switch bid.MType { | ||
| case openrtb2.MarkupBanner: | ||
| return openrtb_ext.BidTypeBanner, nil | ||
| case openrtb2.MarkupVideo: | ||
| return openrtb_ext.BidTypeVideo, nil | ||
| case openrtb2.MarkupAudio: | ||
| return openrtb_ext.BidTypeAudio, nil | ||
| case openrtb2.MarkupNative: | ||
| return openrtb_ext.BidTypeNative, nil | ||
| default: | ||
| return "", &errortypes.BadServerResponse{ | ||
| Message: fmt.Sprintf("unsupported MType %d", bid.MType), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func resolveMacros(bid *openrtb2.Bid) { | ||
| if bid == nil { | ||
| return | ||
| } | ||
| price := strconv.FormatFloat(bid.Price, 'f', -1, 64) | ||
| bid.AdM = strings.ReplaceAll(bid.AdM, "${AUCTION_PRICE}", price) | ||
| bid.NURL = strings.ReplaceAll(bid.NURL, "${AUCTION_PRICE}", price) | ||
| bid.BURL = strings.ReplaceAll(bid.BURL, "${AUCTION_PRICE}", price) | ||
| } | ||
|
|
||
| func getNativeAdm(adm string) (string, error) { | ||
| nativeAdm := make(map[string]interface{}) | ||
| if err := jsonutil.Unmarshal([]byte(adm), &nativeAdm); err != nil { | ||
| return adm, errors.New("unable to unmarshal native adm") | ||
| } | ||
|
|
||
| if _, ok := nativeAdm["native"]; ok { | ||
| value, dataType, _, err := jsonparser.Get([]byte(adm), string(openrtb_ext.BidTypeNative)) | ||
| if err != nil || dataType != jsonparser.Object { | ||
| return adm, errors.New("unable to get native adm") | ||
|
franklong-sa marked this conversation as resolved.
Outdated
|
||
| } | ||
| adm = string(value) | ||
| } | ||
|
|
||
| return adm, nil | ||
| } | ||
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.
Use
jsonutil.Marshalinstead ofencoding/json.The
encoding/jsonimport exists only for this one call (nojson.RawMessagein the file), so switch tojsonutil.Marshal(request)and drop the import.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.
Done. Switched to
jsonutilinstead ofencoding/json.