diff --git a/adapters/ninthdecimal/ninthdecimal.go b/adapters/ninthdecimal/ninthdecimal.go new file mode 100755 index 00000000000..6d7cbdb6a9d --- /dev/null +++ b/adapters/ninthdecimal/ninthdecimal.go @@ -0,0 +1,236 @@ +package ninthdecimal + +import ( + "encoding/json" + "fmt" + "net/http" + "text/template" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/macros" + "github.com/prebid/prebid-server/openrtb_ext" +) + +type NinthDecimalAdapter struct { + EndpointTemplate template.Template +} + +//MakeRequests prepares request information for prebid-server core +func (adapter *NinthDecimalAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + errs := make([]error, 0, len(request.Imp)) + if len(request.Imp) == 0 { + errs = append(errs, &errortypes.BadInput{Message: "No impression in the bid request"}) + return nil, errs + } + pub2impressions, imps, err := getImpressionsInfo(request.Imp) + if len(imps) == 0 { + return nil, err + } + errs = append(errs, err...) + + if len(pub2impressions) == 0 { + return nil, errs + } + + result := make([]*adapters.RequestData, 0, len(pub2impressions)) + for k, imps := range pub2impressions { + bidRequest, err := adapter.buildAdapterRequest(request, &k, imps) + if err != nil { + errs = append(errs, err) + return nil, errs + } else { + result = append(result, bidRequest) + } + } + return result, errs +} + +// getImpressionsInfo checks each impression for validity and returns impressions copy with corresponding exts +func getImpressionsInfo(imps []openrtb.Imp) (map[openrtb_ext.ExtImpNinthDecimal][]openrtb.Imp, []openrtb.Imp, []error) { + errors := make([]error, 0, len(imps)) + resImps := make([]openrtb.Imp, 0, len(imps)) + res := make(map[openrtb_ext.ExtImpNinthDecimal][]openrtb.Imp) + + for _, imp := range imps { + impExt, err := getImpressionExt(&imp) + if err != nil { + errors = append(errors, err) + continue + } + if err := validateImpression(impExt); err != nil { + errors = append(errors, err) + continue + } + //dispatchImpressions + //Group impressions by NinthDecimal-specific parameters `pubid + if err := compatImpression(&imp); err != nil { + errors = append(errors, err) + continue + } + if res[*impExt] == nil { + res[*impExt] = make([]openrtb.Imp, 0) + } + res[*impExt] = append(res[*impExt], imp) + resImps = append(resImps, imp) + } + return res, resImps, errors +} + +func validateImpression(impExt *openrtb_ext.ExtImpNinthDecimal) error { + if impExt.PublisherID == "" { + return &errortypes.BadInput{Message: "No pubid value provided"} + } + return nil +} + +//Alter impression info to comply with NinthDecimal platform requirements +func compatImpression(imp *openrtb.Imp) error { + imp.Ext = nil //do not forward ext to NinthDecimal platform + if imp.Banner != nil { + return compatBannerImpression(imp) + } + return nil +} + +func compatBannerImpression(imp *openrtb.Imp) error { + // Create a copy of the banner, since imp is a shallow copy of the original. + + bannerCopy := *imp.Banner + banner := &bannerCopy + //As banner.w/h are required fields for NinthDecimal platform - take the first format entry + if banner.W == nil || banner.H == nil { + if len(banner.Format) == 0 { + return &errortypes.BadInput{Message: "Expected at least one banner.format entry or explicit w/h"} + } + format := banner.Format[0] + banner.Format = banner.Format[1:] + banner.W = &format.W + banner.H = &format.H + imp.Banner = banner + } + return nil +} + +func getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpNinthDecimal, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, &errortypes.BadInput{ + Message: err.Error(), + } + } + var NinthDecimalExt openrtb_ext.ExtImpNinthDecimal + if err := json.Unmarshal(bidderExt.Bidder, &NinthDecimalExt); err != nil { + return nil, &errortypes.BadInput{ + Message: err.Error(), + } + } + return &NinthDecimalExt, nil +} + +func (adapter *NinthDecimalAdapter) buildAdapterRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpNinthDecimal, imps []openrtb.Imp) (*adapters.RequestData, error) { + newBidRequest := createBidRequest(prebidBidRequest, params, imps) + reqJSON, err := json.Marshal(newBidRequest) + if err != nil { + return nil, err + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + headers.Add("x-openrtb-version", "2.5") + + url, err := adapter.buildEndpointURL(params) + if err != nil { + return nil, err + } + + return &adapters.RequestData{ + Method: "POST", + Uri: url, + Body: reqJSON, + Headers: headers}, nil +} + +func createBidRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpNinthDecimal, imps []openrtb.Imp) *openrtb.BidRequest { + bidRequest := *prebidBidRequest + bidRequest.Imp = imps + for idx := range bidRequest.Imp { + imp := &bidRequest.Imp[idx] + imp.TagID = params.Placement + } + if bidRequest.Site != nil { + // Need to copy Site as Request is a shallow copy + siteCopy := *bidRequest.Site + bidRequest.Site = &siteCopy + bidRequest.Site.Publisher = nil + bidRequest.Site.Domain = "" + } + if bidRequest.App != nil { + // Need to copy App as Request is a shallow copy + appCopy := *bidRequest.App + bidRequest.App = &appCopy + bidRequest.App.Publisher = nil + } + return &bidRequest +} + +// Builds enpoint url based on adapter-specific pub settings from imp.ext +func (adapter *NinthDecimalAdapter) buildEndpointURL(params *openrtb_ext.ExtImpNinthDecimal) (string, error) { + endpointParams := macros.EndpointTemplateParams{PublisherID: params.PublisherID} + return macros.ResolveMacros(adapter.EndpointTemplate, endpointParams) +} + +//MakeBids translates NinthDecimal bid response to prebid-server specific format +func (adapter *NinthDecimalAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + var msg = "" + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + if response.StatusCode != http.StatusOK { + msg = fmt.Sprintf("Unexpected http status code: %d", response.StatusCode) + return nil, []error{&errortypes.BadServerResponse{Message: msg}} + + } + var bidResp openrtb.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + msg = fmt.Sprintf("Bad server response: %d", err) + return nil, []error{&errortypes.BadServerResponse{Message: msg}} + } + if len(bidResp.SeatBid) != 1 { + var msg = fmt.Sprintf("Invalid SeatBids count: %d", len(bidResp.SeatBid)) + return nil, []error{&errortypes.BadServerResponse{Message: msg}} + } + + seatBid := bidResp.SeatBid[0] + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid)) + + for i := 0; i < len(seatBid.Bid); i++ { + bid := seatBid.Bid[i] + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: getMediaTypeForImpID(bid.ImpID, internalRequest.Imp), + }) + } + return bidResponse, nil +} + +// getMediaTypeForImp figures out which media type this bid is for +func getMediaTypeForImpID(impID string, imps []openrtb.Imp) openrtb_ext.BidType { + for _, imp := range imps { + if imp.ID == impID && imp.Video != nil { + return openrtb_ext.BidTypeVideo + } + } + return openrtb_ext.BidTypeBanner +} + +// NewNinthDecimalAdapter to be called in prebid-server core to create NinthDecimal adapter instance +func NewNinthDecimalBidder(endpointTemplate string) adapters.Bidder { + template, err := template.New("endpointTemplate").Parse(endpointTemplate) + if err != nil { + return nil + } + return &NinthDecimalAdapter{EndpointTemplate: *template} +} diff --git a/adapters/ninthdecimal/ninthdecimal_test.go b/adapters/ninthdecimal/ninthdecimal_test.go new file mode 100755 index 00000000000..206455a64be --- /dev/null +++ b/adapters/ninthdecimal/ninthdecimal_test.go @@ -0,0 +1,10 @@ +package ninthdecimal + +import ( + "github.com/prebid/prebid-server/adapters/adapterstest" + "testing" +) + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "ninthdecimaltest", NewNinthDecimalBidder("http://rtb.ninthdecimal.com/xp/get?pubid={{.PublisherID}}")) +} diff --git a/adapters/ninthdecimal/ninthdecimaltest/exemplary/banner.json b/adapters/ninthdecimal/ninthdecimaltest/exemplary/banner.json new file mode 100644 index 00000000000..d2184fa06b6 --- /dev/null +++ b/adapters/ninthdecimal/ninthdecimaltest/exemplary/banner.json @@ -0,0 +1,95 @@ +{ + "mockBidRequest": { + "id": "testid", + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 320, + "h": 250 + }, + { + "w": 320, + "h": 300 + } + ], + "w": 320, + "h": 250 + }, + "ext": { + "bidder": { + "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", + "placement": "dummyplacement" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.ninthdecimal.com/xp/get?pubid=19f1b372c7548ec1fe734d2c9f8dc688", + "body":{ + "id": "testid", + "imp": [{ + "id": "testimpid", + "tagid": "dummyplacement", + "banner": { + "format": [{ + "w": 320, + "h": 250 + }, { + "w": 320, + "h": 300 + }], + "w": 320, + "h": 250 + } + + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "seatbid": [ + { + "bid": [ + { + "crid": "24080", + "adid": "2068416", + "price": 0.01, + "id": "testid", + "impid": "testimpid", + "cid": "8048" + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "crid": "24080", + "adid": "2068416", + "price": 0.01, + "id": "testid", + "impid": "testimpid", + "cid": "8048" + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/ninthdecimal/ninthdecimaltest/exemplary/video.json b/adapters/ninthdecimal/ninthdecimaltest/exemplary/video.json new file mode 100644 index 00000000000..4ad093e0648 --- /dev/null +++ b/adapters/ninthdecimal/ninthdecimaltest/exemplary/video.json @@ -0,0 +1,83 @@ +{ + "mockBidRequest": { + "id": "testid", + "imp": [ + { + "id": "testimpid", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", + "placement": "dummyplacement" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.ninthdecimal.com/xp/get?pubid=19f1b372c7548ec1fe734d2c9f8dc688", + "body":{ + "id": "testid", + "imp": [{ + "id": "testimpid", + "tagid": "dummyplacement", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480 + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "seatbid": [ + { + "bid": [ + { + "crid": "24080", + "adid": "2068416", + "price": 0.01, + "id": "testid", + "impid": "testimpid", + "cid": "8048" + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "crid": "24080", + "adid": "2068416", + "price": 0.01, + "id": "testid", + "impid": "testimpid", + "cid": "8048" + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/ninthdecimal/ninthdecimaltest/params/race/banner.json b/adapters/ninthdecimal/ninthdecimaltest/params/race/banner.json new file mode 100644 index 00000000000..2eed8f2ec4e --- /dev/null +++ b/adapters/ninthdecimal/ninthdecimaltest/params/race/banner.json @@ -0,0 +1,4 @@ +{ + "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", + "placement": "dummyplacement" +} \ No newline at end of file diff --git a/adapters/ninthdecimal/ninthdecimaltest/params/race/video.json b/adapters/ninthdecimal/ninthdecimaltest/params/race/video.json new file mode 100644 index 00000000000..2eed8f2ec4e --- /dev/null +++ b/adapters/ninthdecimal/ninthdecimaltest/params/race/video.json @@ -0,0 +1,4 @@ +{ + "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", + "placement": "dummyplacement" +} \ No newline at end of file diff --git a/adapters/ninthdecimal/ninthdecimaltest/supplemental/checkImp.json b/adapters/ninthdecimal/ninthdecimaltest/supplemental/checkImp.json new file mode 100644 index 00000000000..ca48812b4df --- /dev/null +++ b/adapters/ninthdecimal/ninthdecimaltest/supplemental/checkImp.json @@ -0,0 +1,14 @@ +{ + "mockBidRequest": { + "id": "testid", + "site": { + "id": "test", + "domain": "test.com" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "No impression in the bid request", + "comparison": "literal" + }] + } \ No newline at end of file diff --git a/adapters/ninthdecimal/ninthdecimaltest/supplemental/compat.json b/adapters/ninthdecimal/ninthdecimaltest/supplemental/compat.json new file mode 100644 index 00000000000..ff33b59cff9 --- /dev/null +++ b/adapters/ninthdecimal/ninthdecimaltest/supplemental/compat.json @@ -0,0 +1,80 @@ +{ + "mockBidRequest": { + "id": "testid", + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [{ + "w": 320, + "h": 250 + }] + }, + "ext": { + "bidder": { + "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", + "placement": "dummyplacement" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.ninthdecimal.com/xp/get?pubid=19f1b372c7548ec1fe734d2c9f8dc688", + "body":{ + "id": "testid", + "imp": [{ + "banner": { + "h": 250, + "w": 320 + }, + "id": "testimpid", + "tagid": "dummyplacement" + + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "seatbid": [ + { + "bid": [ + { + "crid": "24080", + "adid": "2068416", + "price": 0.01, + "id": "testid", + "impid": "testimpid", + "cid": "8048" + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "crid": "24080", + "adid": "2068416", + "price": 0.01, + "id": "testid", + "impid": "testimpid", + "cid": "8048" + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/ninthdecimal/ninthdecimaltest/supplemental/ext.json b/adapters/ninthdecimal/ninthdecimaltest/supplemental/ext.json new file mode 100644 index 00000000000..3cfb878bd47 --- /dev/null +++ b/adapters/ninthdecimal/ninthdecimaltest/supplemental/ext.json @@ -0,0 +1,33 @@ +{ + "mockBidRequest": { + "id": "testid", + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 320, + "h": 250 + }, + { + "w": 320, + "h": 300 + } + ], + "w": 320, + "h": 250 + }, + "ext": { + "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", + "placement": "dummyplacement" + } + } + ] + }, +"expectedMakeRequestsErrors": [ + { + "value": "unexpected end of JSON input", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/ninthdecimal/ninthdecimaltest/supplemental/missingpub.json b/adapters/ninthdecimal/ninthdecimaltest/supplemental/missingpub.json new file mode 100644 index 00000000000..b088917afa3 --- /dev/null +++ b/adapters/ninthdecimal/ninthdecimaltest/supplemental/missingpub.json @@ -0,0 +1,35 @@ +{ + "mockBidRequest": { + "id": "testid", + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 320, + "h": 250 + }, + { + "w": 320, + "h": 300 + } + ], + "w": 320, + "h": 250 + }, + "ext": { + "bidder": { + "pubid": "", + "placement": "dummyplacement" + } + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "No pubid value provided", + "comparison": "literal" + }] + } \ No newline at end of file diff --git a/adapters/ninthdecimal/ninthdecimaltest/supplemental/responseCode.json b/adapters/ninthdecimal/ninthdecimaltest/supplemental/responseCode.json new file mode 100644 index 00000000000..a68db2b823e --- /dev/null +++ b/adapters/ninthdecimal/ninthdecimaltest/supplemental/responseCode.json @@ -0,0 +1,78 @@ +{ + "mockBidRequest": { + "id": "testid", + "site": { + "id": "test" + }, + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 320, + "h": 250 + }, + { + "w": 320, + "h": 300 + } + ], + "w": 320, + "h": 250 + }, + "ext": { + "bidder": { + "pubid": "yu", + "placement": "dummyplacement" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.ninthdecimal.com/xp/get?pubid=yu", + "body": { + "id": "testid", + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 320 + }, + { + "h": 300, + "w": 320 + } + ], + "h": 250, + "w": 320 + }, + "id": "testimpid", + "tagid": "dummyplacement" + } + ], + "site": { + "id": "test" + } + } + }, + "mockResponse": { + "body": { + "seatbid": [] + } + } + } + + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected http status code: 0", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/ninthdecimal/ninthdecimaltest/supplemental/responsebid.json b/adapters/ninthdecimal/ninthdecimaltest/supplemental/responsebid.json new file mode 100644 index 00000000000..c03aa1b2d89 --- /dev/null +++ b/adapters/ninthdecimal/ninthdecimaltest/supplemental/responsebid.json @@ -0,0 +1,79 @@ +{ + "mockBidRequest": { + "id": "testid", + "site": { + "id": "test" + }, + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 320, + "h": 250 + }, + { + "w": 320, + "h": 300 + } + ], + "w": 320, + "h": 250 + }, + "ext": { + "bidder": { + "pubid": "yu", + "placement": "dummyplacement" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.ninthdecimal.com/xp/get?pubid=yu", + "body": { + "id": "testid", + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 320 + }, + { + "h": 300, + "w": 320 + } + ], + "h": 250, + "w": 320 + }, + "id": "testimpid", + "tagid": "dummyplacement" + } + ], + "site": { + "id": "test" + } + } + }, + "mockResponse": { + "status":200, + "body": { + "seatbid": [] + } + } + } + + ], + "expectedMakeBidsErrors": [ + { + "value": "Invalid SeatBids count: 0", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/ninthdecimal/ninthdecimaltest/supplemental/site.json b/adapters/ninthdecimal/ninthdecimaltest/supplemental/site.json new file mode 100644 index 00000000000..a3e160e4bd8 --- /dev/null +++ b/adapters/ninthdecimal/ninthdecimaltest/supplemental/site.json @@ -0,0 +1,103 @@ +{ + "mockBidRequest": { + "id": "testid", + "site": { + "id": "test" + }, + "imp": [ + { + "id": "testimpid", + "banner": { + "format": [ + { + "w": 320, + "h": 250 + }, + { + "w": 320, + "h": 300 + } + ], + "w": 320, + "h": 250 + }, + "ext": { + "bidder": { + "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", + "placement": "dummyplacement" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://rtb.ninthdecimal.com/xp/get?pubid=19f1b372c7548ec1fe734d2c9f8dc688", + "body": { + "id": "testid", + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 320 + }, + { + "h": 300, + "w": 320 + } + ], + "h": 250, + "w": 320 + }, + "id": "testimpid", + "tagid": "dummyplacement" + } + ], + "site": { + "id": "test" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "seatbid": [ + { + "bid": [ + { + "crid": "24080", + "adid": "2068416", + "price": 0.01, + "id": "testid", + "impid": "testimpid", + "cid": "8048" + } + ] + } + ] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "crid": "24080", + "adid": "2068416", + "price": 0.01, + "id": "testid", + "impid": "testimpid", + "cid": "8048" + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/ninthdecimal/ninthdecimaltest/supplemental/size.json b/adapters/ninthdecimal/ninthdecimaltest/supplemental/size.json new file mode 100644 index 00000000000..77228559eee --- /dev/null +++ b/adapters/ninthdecimal/ninthdecimaltest/supplemental/size.json @@ -0,0 +1,28 @@ +{ + "mockBidRequest": { + "id": "testid", + "site": { + "id": "test", + "domain": "test.com" + }, + "imp": [ + { + "id": "testimpid", + "banner": { + + }, + "ext": { + "bidder": { + "pubid": "19f1b372c7548ec1fe734d2c9f8dc688", + "placement": "dummyplacement" + } + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "Expected at least one banner.format entry or explicit w/h", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/ninthdecimal/params_test.go b/adapters/ninthdecimal/params_test.go new file mode 100755 index 00000000000..8d3ef3d706f --- /dev/null +++ b/adapters/ninthdecimal/params_test.go @@ -0,0 +1,45 @@ +package ninthdecimal + +import ( + "encoding/json" + "github.com/prebid/prebid-server/openrtb_ext" + "testing" +) + +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.BidderNinthDecimal, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected NinthDecimal 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.BidderNinthDecimal, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"pubid": "19f1b372c7548ec1fe734d2c9f8dc688"}`, +} + +var invalidParams = []string{ + `{"publisher": "19f1b372c7548ec1fe734d2c9f8dc688"}`, + `nil`, + ``, + `[]`, + `true`, +} diff --git a/adapters/ninthdecimal/usersync.go b/adapters/ninthdecimal/usersync.go new file mode 100755 index 00000000000..7a8d029cfa9 --- /dev/null +++ b/adapters/ninthdecimal/usersync.go @@ -0,0 +1,12 @@ +package ninthdecimal + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewNinthDecimalSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("ninthdecimal", 0, temp, adapters.SyncTypeIframe) +} diff --git a/adapters/ninthdecimal/usersync_test.go b/adapters/ninthdecimal/usersync_test.go new file mode 100755 index 00000000000..ee121434f29 --- /dev/null +++ b/adapters/ninthdecimal/usersync_test.go @@ -0,0 +1,31 @@ +package ninthdecimal + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestNinthDecimalSyncer(t *testing.T) { + syncURL := "https://rtb.ninthdecimal.com/xp/user-sync?acctid={aid}&&redirect=localhost/setuid?bidder=ninthdecimal&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&uid=$UID" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewNinthDecimalSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "1", + Consent: "A", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "https://rtb.ninthdecimal.com/xp/user-sync?acctid={aid}&&redirect=localhost/setuid?bidder=ninthdecimal&gdpr=1&gdpr_consent=A&uid=$UID", syncInfo.URL) + assert.Equal(t, "iframe", syncInfo.Type) + assert.EqualValues(t, 0, syncer.GDPRVendorID()) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/config/config.go b/config/config.go old mode 100644 new mode 100755 index 2ea91bfa24b..02745bbb8d2 --- a/config/config.go +++ b/config/config.go @@ -523,6 +523,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderMarsmedia, "https://dmp.rtbsrv.com/dmp/profiles/cm?p_id=179&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dmarsmedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderMgid, "https://cm.mgid.com/m?cdsp=363893&adu="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dmgid%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Bmuidn%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderNanoInteractive, "https://ad.audiencemanager.de/hbs/cookie_sync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dnanointeractive%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderNinthDecimal, "https://rtb.ninthdecimal.com/xp/user-sync?acctid={aid}&&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dninthdecimal%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderOpenx, "https://rtb.openx.net/sync/prebid?r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dopenx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderPubmatic, "https://ads.pubmatic.com/AdServer/js/user_sync.html?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&predirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dpubmatic%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderPulsepoint, "https://bh.contextweb.com/rtset?pid=561205&ev=1&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dpulsepoint%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25VGUID%25%25") @@ -720,6 +721,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.marsmedia.endpoint", "https://bid306.rtbsrv.com/bidder/?bid=f3xtet") v.SetDefault("adapters.mgid.endpoint", "https://prebid.mgid.com/prebid/") v.SetDefault("adapters.nanointeractive.endpoint", "https://ad.audiencemanager.de/hbs") + v.SetDefault("adapters.ninthdecimal.endpoint", "http://rtb.ninthdecimal.com/xp/get?pubid={{.PublisherID}}") v.SetDefault("adapters.openx.endpoint", "http://rtb.openx.net/prebid") v.SetDefault("adapters.pubmatic.endpoint", "https://hbopenbid.pubmatic.com/translator?source=prebid-server") v.SetDefault("adapters.pubnative.endpoint", "http://dsp.pubnative.net/bid/v1/request") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go old mode 100644 new mode 100755 index 698824c3fe2..0b0c42d88b6 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -41,6 +41,7 @@ import ( "github.com/prebid/prebid-server/adapters/marsmedia" "github.com/prebid/prebid-server/adapters/mgid" "github.com/prebid/prebid-server/adapters/nanointeractive" + "github.com/prebid/prebid-server/adapters/ninthdecimal" "github.com/prebid/prebid-server/adapters/openx" "github.com/prebid/prebid-server/adapters/pubmatic" "github.com/prebid/prebid-server/adapters/pubnative" @@ -111,6 +112,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderMarsmedia: marsmedia.NewMarsmediaBidder(cfg.Adapters[string(openrtb_ext.BidderMarsmedia)].Endpoint), openrtb_ext.BidderMgid: mgid.NewMgidBidder(cfg.Adapters[string(openrtb_ext.BidderMgid)].Endpoint), openrtb_ext.BidderNanoInteractive: nanointeractive.NewNanoIneractiveBidder(cfg.Adapters[string(openrtb_ext.BidderNanoInteractive)].Endpoint), + openrtb_ext.BidderNinthDecimal: ninthdecimal.NewNinthDecimalBidder(cfg.Adapters[string(openrtb_ext.BidderNinthDecimal)].Endpoint), openrtb_ext.BidderOpenx: openx.NewOpenxBidder(cfg.Adapters[string(openrtb_ext.BidderOpenx)].Endpoint), openrtb_ext.BidderPubmatic: pubmatic.NewPubmaticBidder(client, cfg.Adapters[string(openrtb_ext.BidderPubmatic)].Endpoint), openrtb_ext.BidderPubnative: pubnative.NewPubnativeBidder(cfg.Adapters[string(openrtb_ext.BidderPubnative)].Endpoint), diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go old mode 100644 new mode 100755 index 5b0046ce096..f8973469e40 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -59,6 +59,7 @@ const ( BidderMarsmedia BidderName = "marsmedia" BidderMgid BidderName = "mgid" BidderNanoInteractive BidderName = "nanointeractive" + BidderNinthDecimal BidderName = "ninthdecimal" BidderOpenx BidderName = "openx" BidderPubmatic BidderName = "pubmatic" BidderPubnative BidderName = "pubnative" @@ -125,6 +126,7 @@ var BidderMap = map[string]BidderName{ "marsmedia": BidderMarsmedia, "mgid": BidderMgid, "nanointeractive": BidderNanoInteractive, + "ninthdecimal": BidderNinthDecimal, "openx": BidderOpenx, "pubmatic": BidderPubmatic, "pubnative": BidderPubnative, diff --git a/openrtb_ext/imp_ninthdecimal.go b/openrtb_ext/imp_ninthdecimal.go new file mode 100755 index 00000000000..8fb794dbdf2 --- /dev/null +++ b/openrtb_ext/imp_ninthdecimal.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ExtImpNinthDecimal struct { + PublisherID string `json:"pubid"` + Placement string `json:"placement,omitempty"` +} diff --git a/static/bidder-info/ninthdecimal.yaml b/static/bidder-info/ninthdecimal.yaml new file mode 100755 index 00000000000..eda7d222a5f --- /dev/null +++ b/static/bidder-info/ninthdecimal.yaml @@ -0,0 +1,13 @@ +maintainer: + email: "abudig@ninthdecimal.com" +capabilities: + site: + mediaTypes: + - banner + - video + + app: + mediaTypes: + - banner + - video + diff --git a/static/bidder-params/ninthdecimal.json b/static/bidder-params/ninthdecimal.json new file mode 100755 index 00000000000..f230361d77e --- /dev/null +++ b/static/bidder-params/ninthdecimal.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "NinthDecimal Adapter Params", + "description": "A schema which validates params accepted by the NinthDecimal adapter", + "type": "object", + "properties": { + "pubid": { + "type": "string", + "description": "An id used to identify NinthDecimal publisher.", + "minLength": 8 + }, + "placement": { + "type": "string", + "description": "A placement created on adserver." + } + }, + "required": ["pubid"] +} diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go old mode 100644 new mode 100755 index 995f573adca..3c0931f4dca --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -35,6 +35,7 @@ import ( "github.com/prebid/prebid-server/adapters/marsmedia" "github.com/prebid/prebid-server/adapters/mgid" "github.com/prebid/prebid-server/adapters/nanointeractive" + "github.com/prebid/prebid-server/adapters/ninthdecimal" "github.com/prebid/prebid-server/adapters/openx" "github.com/prebid/prebid-server/adapters/pubmatic" "github.com/prebid/prebid-server/adapters/pulsepoint" @@ -100,6 +101,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderMarsmedia, marsmedia.NewMarsmediaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderMgid, mgid.NewMgidSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderNanoInteractive, nanointeractive.NewNanoInteractiveSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderNinthDecimal, ninthdecimal.NewNinthDecimalSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderOpenx, openx.NewOpenxSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderPubmatic, pubmatic.NewPubmaticSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderPulsepoint, pulsepoint.NewPulsepointSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go old mode 100644 new mode 100755 index c84b3d9b6b9..3e1de8ada74 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -44,6 +44,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderMarsmedia): syncConfig, string(openrtb_ext.BidderMgid): syncConfig, string(openrtb_ext.BidderNanoInteractive): syncConfig, + string(openrtb_ext.BidderNinthDecimal): syncConfig, string(openrtb_ext.BidderOpenx): syncConfig, string(openrtb_ext.BidderPubmatic): syncConfig, string(openrtb_ext.BidderPulsepoint): syncConfig,