diff --git a/adapters/datablocks/datablocks.go b/adapters/datablocks/datablocks.go
new file mode 100644
index 00000000000..ec4ab6ceb2d
--- /dev/null
+++ b/adapters/datablocks/datablocks.go
@@ -0,0 +1,188 @@
+package datablocks
+
+import (
+ "encoding/json"
+ "fmt"
+ "github.com/golang/glog"
+ "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"
+ "net/http"
+ "strconv"
+ "text/template"
+)
+
+type DatablocksAdapter struct {
+ EndpointTemplate template.Template
+}
+
+func (a *DatablocksAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+
+ errs := make([]error, 0, len(request.Imp))
+ headers := http.Header{
+ "Content-Type": {"application/json"},
+ "Accept": {"application/json"},
+ }
+
+ // Pull the host and source ID info from the bidder params.
+ reqImps, err := splitImpressions(request.Imp)
+
+ if err != nil {
+ errs = append(errs, err)
+ }
+
+ requests := []*adapters.RequestData{}
+
+ for reqExt, reqImp := range reqImps {
+ request.Imp = reqImp
+ reqJson, err := json.Marshal(request)
+
+ if err != nil {
+ errs = append(errs, err)
+ continue
+ }
+
+ urlParams := macros.EndpointTemplateParams{Host: reqExt.Host, SourceId: strconv.Itoa(reqExt.SourceId)}
+ url, err := macros.ResolveMacros(a.EndpointTemplate, urlParams)
+
+ if err != nil {
+ errs = append(errs, err)
+ continue
+ }
+
+ request := adapters.RequestData{
+ Method: "POST",
+ Uri: url,
+ Body: reqJson,
+ Headers: headers}
+
+ requests = append(requests, &request)
+ }
+
+ return requests, errs
+}
+
+/*
+internal original request in OpenRTB, external = result of us having converted it (what comes out of MakeRequests)
+*/
+func (a *DatablocksAdapter) MakeBids(
+ internalRequest *openrtb.BidRequest,
+ externalRequest *adapters.RequestData,
+ response *adapters.ResponseData,
+) (*adapters.BidderResponse, []error) {
+
+ if response.StatusCode == http.StatusNoContent {
+ return nil, nil
+ }
+
+ if response.StatusCode != http.StatusOK {
+ return nil, []error{&errortypes.BadServerResponse{
+ Message: fmt.Sprintf("ERR, response with status %d", response.StatusCode),
+ }}
+ }
+
+ var bidResp openrtb.BidResponse
+
+ if err := json.Unmarshal(response.Body, &bidResp); err != nil {
+ return nil, []error{err}
+ }
+
+ bidResponse := adapters.NewBidderResponse()
+ bidResponse.Currency = bidResp.Cur
+
+ for _, seatBid := range bidResp.SeatBid {
+ for i := 0; i < len(seatBid.Bid); i++ {
+ bid := seatBid.Bid[i]
+ bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+ Bid: &bid,
+ BidType: getMediaType(bid.ImpID, internalRequest.Imp),
+ })
+ }
+ }
+
+ return bidResponse, nil
+}
+
+func splitImpressions(imps []openrtb.Imp) (map[openrtb_ext.ExtImpDatablocks][]openrtb.Imp, error) {
+
+ var m = make(map[openrtb_ext.ExtImpDatablocks][]openrtb.Imp)
+
+ for _, imp := range imps {
+ bidderParams, err := getBidderParams(&imp)
+ if err != nil {
+ return nil, err
+ }
+
+ v, ok := m[*bidderParams]
+ if ok {
+ m[*bidderParams] = append(v, imp)
+ } else {
+ m[*bidderParams] = []openrtb.Imp{imp}
+ }
+ }
+
+ return m, nil
+}
+
+func getBidderParams(imp *openrtb.Imp) (*openrtb_ext.ExtImpDatablocks, error) {
+ var bidderExt adapters.ExtImpBidder
+ if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+ return nil, &errortypes.BadInput{
+ Message: fmt.Sprintf("Missing bidder ext: %s", err.Error()),
+ }
+ }
+ var datablocksExt openrtb_ext.ExtImpDatablocks
+ if err := json.Unmarshal(bidderExt.Bidder, &datablocksExt); err != nil {
+ return nil, &errortypes.BadInput{
+ Message: fmt.Sprintf("Cannot Resolve host or sourceId: %s", err.Error()),
+ }
+ }
+
+ if datablocksExt.SourceId < 1 {
+ return nil, &errortypes.BadInput{
+ Message: "Invalid/Missing SourceId",
+ }
+ }
+
+ if len(datablocksExt.Host) < 1 {
+ return nil, &errortypes.BadInput{
+ Message: "Invalid/Missing Host",
+ }
+ }
+
+ return &datablocksExt, nil
+}
+
+func getMediaType(impID string, imps []openrtb.Imp) openrtb_ext.BidType {
+
+ bidType := openrtb_ext.BidTypeBanner
+
+ for _, imp := range imps {
+ if imp.ID == impID {
+ if imp.Video != nil {
+ bidType = openrtb_ext.BidTypeVideo
+ break
+ } else if imp.Native != nil {
+ bidType = openrtb_ext.BidTypeNative
+ break
+ } else {
+ bidType = openrtb_ext.BidTypeBanner
+ break
+ }
+ }
+ }
+
+ return bidType
+}
+
+func NewDatablocksBidder(endpoint string) *DatablocksAdapter {
+ template, err := template.New("endpointTemplate").Parse(endpoint)
+ if err != nil {
+ glog.Fatal("Unable to parse endpoint url template")
+ return nil
+ }
+
+ return &DatablocksAdapter{EndpointTemplate: *template}
+}
diff --git a/adapters/datablocks/datablocks_test.go b/adapters/datablocks/datablocks_test.go
new file mode 100644
index 00000000000..28c42016087
--- /dev/null
+++ b/adapters/datablocks/datablocks_test.go
@@ -0,0 +1,11 @@
+package datablocks
+
+import (
+ "testing"
+
+ "github.com/prebid/prebid-server/adapters/adapterstest"
+)
+
+func TestJsonSamples(t *testing.T) {
+ adapterstest.RunJSONBidderTest(t, "datablockstest", NewDatablocksBidder("http://{{.Host}}/openrtb2?sid={{.SourceId}}"))
+}
diff --git a/adapters/datablocks/datablockstest/exemplary/multi-request.json b/adapters/datablocks/datablockstest/exemplary/multi-request.json
new file mode 100644
index 00000000000..286e058a72e
--- /dev/null
+++ b/adapters/datablocks/datablockstest/exemplary/multi-request.json
@@ -0,0 +1,160 @@
+{
+ "mockBidRequest":
+ {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner":
+ {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "search.nutella.datablocks.net",
+ "sourceId": 906295
+ }
+ }
+ },{
+ "id": "some-impression-id2",
+ "banner":
+ {
+ "format": [{
+ "w": 300,
+ "h": 600
+ }]
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "search.nutella.datablocks.net",
+ "sourceId": 906295
+ }
+ }
+ }],
+ "site":
+ {
+ "page": "prebid.org"
+ },
+ "device":
+ {
+ "ip": "8.8.8.10"
+ },
+ "at": 1,
+ "tmax": 500
+ },
+ "httpCalls": [
+ {
+ "expectedRequest":
+ {
+ "uri": "http://search.nutella.datablocks.net/openrtb2?sid=906295",
+ "body":
+ {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner":
+ {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "search.nutella.datablocks.net",
+ "sourceId": 906295
+ }
+ }
+ },{
+ "id": "some-impression-id2",
+ "banner":
+ {
+ "format": [
+ {
+ "w": 300,
+ "h": 600
+ }]
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "search.nutella.datablocks.net",
+ "sourceId": 906295
+ }
+ }
+ }],
+ "site":
+ {
+ "page": "prebid.org"
+ },
+ "device":
+ {
+ "ip": "8.8.8.10"
+ },
+ "at": 1,
+ "tmax": 500
+ }
+ },
+ "mockResponse":
+ {
+ "status": 200,
+ "body":
+ {
+ "id": "some-request-id",
+ "bidid": "183975330-5-29038-2",
+ "seatbid": [
+ {
+ "seat": "906295",
+ "bid": [
+ {
+ "id": "2181314349",
+ "impid": "some-impression-id",
+ "adm": "
Datablocks provides world class \"Software as a Service\" (SaaS) solutions to its clients.
www.datablocks.net ",
+ "price": 13.37,
+ "cid": "906293",
+ "adid": "906297",
+ "crid": "906299",
+ "w": 300,
+ "h": 250
+ }]
+ }],
+ "cur": "USD",
+ "ext":
+ {}
+ }
+ }
+ }],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid":
+ {
+ "id": "2181314349",
+ "impid": "some-impression-id",
+ "adm": "Datablocks provides world class \"Software as a Service\" (SaaS) solutions to its clients.
www.datablocks.net ",
+ "price": 13.37,
+ "cid": "906293",
+ "adid": "906297",
+ "crid": "906299",
+ "w": 300,
+ "h": 250
+ },
+ "type": "banner"
+ }]
+ }]
+}
\ No newline at end of file
diff --git a/adapters/datablocks/datablockstest/exemplary/native.json b/adapters/datablocks/datablockstest/exemplary/native.json
new file mode 100644
index 00000000000..9487079c734
--- /dev/null
+++ b/adapters/datablocks/datablockstest/exemplary/native.json
@@ -0,0 +1,123 @@
+{
+ "mockBidRequest":
+ {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "native":
+ {
+ "request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":500}},{\"id\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":2,\"data\":{\"type\":1,\"len\":200}},{\"id\":3,\"data\":{\"type\":2,\"len\":15000}},{\"id\":4,\"data\":{\"type\":6,\"len\":40}}]}",
+ "ver": "1.1"
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "search.nutella.datablocks.net",
+ "sourceId": 906295
+ }
+ }
+ }],
+ "site":
+ {
+ "page": "prebid.org"
+ },
+ "device":
+ {
+ "ip": "8.8.8.10"
+ },
+ "user":
+ {
+ "buyeruid": "4610943261"
+ },
+ "at": 1,
+ "tmax": 500
+ },
+ "httpcalls": [
+ {
+ "expectedRequest":
+ {
+ "uri": "http://search.nutella.datablocks.net/openrtb2?sid=906295",
+ "body":
+ {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "native":
+ {
+ "request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":500}},{\"id\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":2,\"data\":{\"type\":1,\"len\":200}},{\"id\":3,\"data\":{\"type\":2,\"len\":15000}},{\"id\":4,\"data\":{\"type\":6,\"len\":40}}]}",
+ "ver": "1.1"
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "search.nutella.datablocks.net",
+ "sourceId": 906295
+ }
+ }
+ }],
+ "site":
+ {
+ "page": "prebid.org"
+ },
+ "device":
+ {
+ "ip": "8.8.8.10"
+ },
+ "user":
+ {
+ "buyeruid": "4610943261"
+ },
+ "at": 1,
+ "tmax": 500
+ }
+ },
+ "mockResponse":
+ {
+ "status":200,
+ "body": {
+ "id": "some-request-id",
+ "bidid": "183975330-3-29038-2",
+ "seatbid": [
+ {
+ "seat": "906295",
+ "bid": [
+ {
+ "id": "2181314346",
+ "impid": "some-impression-id",
+ "adm": "{\"native\":{\"ver\":\"1.2\",\"assets\":[{ \"id\":0,\"required\":1,\"title\":{\"text\":\"Datablocks Inc.\"}},{ \"id\":3,\"required\":0,\"data\":{\"value\":\"Datablocks provides world class \\\"Software as a Service\\\" (SaaS) solutions to its clients.\"}}],\"link\":{\"url\":\"https://track.nutella.datablocks.net/c/267237/?fcid=43154325321\"},\"imptrackers\":[\"https://impression.nutella.datablocks.net/i/267237/?fcid=43154325321&pixel=1\"],\"jstracker\":[]}}",
+ "price": 13.37,
+ "cid": "906293",
+ "adid": "906297",
+ "crid": "906299",
+ "ext": {}
+ }]
+ }],
+ "cur": "USD",
+ "ext": {}
+ }
+ }
+ }],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "2181314346",
+ "impid": "some-impression-id",
+ "adm": "{\"native\":{\"ver\":\"1.2\",\"assets\":[{ \"id\":0,\"required\":1,\"title\":{\"text\":\"Datablocks Inc.\"}},{ \"id\":3,\"required\":0,\"data\":{\"value\":\"Datablocks provides world class \\\"Software as a Service\\\" (SaaS) solutions to its clients.\"}}],\"link\":{\"url\":\"https://track.nutella.datablocks.net/c/267237/?fcid=43154325321\"},\"imptrackers\":[\"https://impression.nutella.datablocks.net/i/267237/?fcid=43154325321&pixel=1\"],\"jstracker\":[]}}",
+ "price": 13.37,
+ "cid": "906293",
+ "adid": "906297",
+ "crid": "906299",
+ "ext": {}
+ },
+ "type":"native"
+ }
+ ]
+ }]
+}
\ No newline at end of file
diff --git a/adapters/datablocks/datablockstest/exemplary/simple-banner.json b/adapters/datablocks/datablockstest/exemplary/simple-banner.json
new file mode 100644
index 00000000000..289549ab588
--- /dev/null
+++ b/adapters/datablocks/datablockstest/exemplary/simple-banner.json
@@ -0,0 +1,133 @@
+{
+ "mockBidRequest":
+ {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner":
+ {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ },
+ {
+ "w": 300,
+ "h": 600
+ }]
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "search.nutella.datablocks.net",
+ "sourceId": 906295
+ }
+ }
+ }],
+ "site":
+ {
+ "page": "prebid.org"
+ },
+ "device":
+ {
+ "ip": "8.8.8.10"
+ },
+ "at": 1,
+ "tmax": 500
+ },
+ "httpCalls": [
+ {
+ "expectedRequest":
+ {
+ "uri": "http://search.nutella.datablocks.net/openrtb2?sid=906295",
+ "body":
+ {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner":
+ {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ },
+ {
+ "w": 300,
+ "h": 600
+ }]
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "search.nutella.datablocks.net",
+ "sourceId": 906295
+ }
+ }
+ }],
+ "site":
+ {
+ "page": "prebid.org"
+ },
+ "device":
+ {
+ "ip": "8.8.8.10"
+ },
+ "at": 1,
+ "tmax": 500
+ }
+ },
+ "mockResponse":
+ {
+ "status": 200,
+ "body":
+ {
+ "id": "some-request-id",
+ "bidid": "183975330-5-29038-2",
+ "seatbid": [
+ {
+ "seat": "906295",
+ "bid": [
+ {
+ "id": "2181314349",
+ "impid": "some-impression-id",
+ "adm": "Datablocks provides world class \"Software as a Service\" (SaaS) solutions to its clients.
www.datablocks.net ",
+ "price": 13.37,
+ "cid": "906293",
+ "adid": "906297",
+ "crid": "906299",
+ "w": 300,
+ "h": 250
+ }]
+ }],
+ "cur": "USD",
+ "ext":
+ {}
+ }
+ }
+ }],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid":
+ {
+ "id": "2181314349",
+ "impid": "some-impression-id",
+ "adm": "Datablocks provides world class \"Software as a Service\" (SaaS) solutions to its clients.
www.datablocks.net ",
+ "price": 13.37,
+ "cid": "906293",
+ "adid": "906297",
+ "crid": "906299",
+ "w": 300,
+ "h": 250
+ },
+ "type": "banner"
+ }]
+ }]
+}
\ No newline at end of file
diff --git a/adapters/datablocks/datablockstest/exemplary/simple-video.json b/adapters/datablocks/datablockstest/exemplary/simple-video.json
new file mode 100644
index 00000000000..6f8073107f3
--- /dev/null
+++ b/adapters/datablocks/datablockstest/exemplary/simple-video.json
@@ -0,0 +1,138 @@
+{
+ "mockBidRequest":
+ {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "video":
+ {
+ "mimes":[
+ "video/x-flv"
+ ],
+ "w": 500,
+ "h": 400,
+ "minduration": 30
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "search.nutella.datablocks.net",
+ "sourceId": 906295
+ }
+ }
+ }],
+ "site":
+ {
+ "page": "prebid.org"
+ },
+ "device":
+ {
+ "ip": "8.8.8.10"
+ },
+ "at": 1,
+ "tmax": 500
+ },
+
+ "httpCalls": [
+ {
+ "expectedRequest":
+ {
+ "uri": "http://search.nutella.datablocks.net/openrtb2?sid=906295",
+ "body":
+ {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "video":
+ {
+ "mimes":[
+ "video/x-flv"
+ ],
+ "w": 500,
+ "h": 400,
+ "minduration": 30
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "search.nutella.datablocks.net",
+ "sourceId": 906295
+ }
+ }
+ }],
+ "site":
+ {
+ "page": "prebid.org"
+ },
+ "device":
+ {
+ "ip": "8.8.8.10"
+ },
+ "at": 1,
+ "tmax": 500
+ }
+ },
+ "mockResponse":
+ {
+ "status": 200,
+ "body":
+ {
+ "id": "some-request-id",
+ "bidid": "183975330-4-29038-2",
+ "seatbid": [
+ {
+ "seat": "906295",
+ "bid": [
+ {
+ "id": "2181314347",
+ "impid": "some-impression-id",
+ "nurl": "https://impression.nutella.datablocks.net/wm/267237/?fcid=2181314347",
+ "price": 13.37,
+ "cid": "906293",
+ "adid": "906297",
+ "crid": "906729",
+ "w": 500,
+ "h": 400,
+ "ext":
+ {
+ "type": "CPM"
+ }
+ }]
+ }],
+ "cur": "USD",
+ "ext":
+ {}
+ }
+ }
+ }],
+
+
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid":
+ {
+ "id": "2181314347",
+ "impid": "some-impression-id",
+ "price": 13.37,
+ "nurl": "https://impression.nutella.datablocks.net/wm/267237/?fcid=2181314347",
+ "adid": "906297",
+ "cid": "906293",
+ "crid": "906729",
+ "w": 500,
+ "h": 400,
+ "ext":
+ {
+ "type": "CPM"
+ }
+ },
+ "type": "video"
+ }]
+ }]
+}
\ No newline at end of file
diff --git a/adapters/datablocks/datablockstest/params/race/banner.json b/adapters/datablocks/datablockstest/params/race/banner.json
new file mode 100644
index 00000000000..0433de8388d
--- /dev/null
+++ b/adapters/datablocks/datablockstest/params/race/banner.json
@@ -0,0 +1,4 @@
+{
+ "sourceId": 906295,
+ "host": "search.nutella.datablocks.net"
+}
\ No newline at end of file
diff --git a/adapters/datablocks/datablockstest/params/race/native.json b/adapters/datablocks/datablockstest/params/race/native.json
new file mode 100644
index 00000000000..0433de8388d
--- /dev/null
+++ b/adapters/datablocks/datablockstest/params/race/native.json
@@ -0,0 +1,4 @@
+{
+ "sourceId": 906295,
+ "host": "search.nutella.datablocks.net"
+}
\ No newline at end of file
diff --git a/adapters/datablocks/datablockstest/params/race/video.json b/adapters/datablocks/datablockstest/params/race/video.json
new file mode 100644
index 00000000000..0433de8388d
--- /dev/null
+++ b/adapters/datablocks/datablockstest/params/race/video.json
@@ -0,0 +1,4 @@
+{
+ "sourceId": 906295,
+ "host": "search.nutella.datablocks.net"
+}
\ No newline at end of file
diff --git a/adapters/datablocks/datablockstest/supplemental/bad-host.json b/adapters/datablocks/datablockstest/supplemental/bad-host.json
new file mode 100644
index 00000000000..cee5efbe760
--- /dev/null
+++ b/adapters/datablocks/datablockstest/supplemental/bad-host.json
@@ -0,0 +1,33 @@
+{
+ "mockBidRequest": {
+ "id": "bad-host-test",
+ "imp": [
+ {
+ "id": "bad-host-test-imp",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "",
+ "sourceId": 123
+ }
+ }
+ }
+ ]
+ },
+
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "Invalid/Missing Host",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/datablocks/datablockstest/supplemental/bad-response-body.json b/adapters/datablocks/datablockstest/supplemental/bad-response-body.json
new file mode 100644
index 00000000000..c7ecf3c0eaa
--- /dev/null
+++ b/adapters/datablocks/datablockstest/supplemental/bad-response-body.json
@@ -0,0 +1,88 @@
+{
+ "mockBidRequest":
+ {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner":
+ {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "search.nutella.datablocks.net",
+ "sourceId": 123
+ }
+ }
+ }],
+ "site":
+ {
+ "page": "prebid.org"
+ },
+ "device":
+ {
+ "ip": "8.8.8.10"
+ },
+ "at": 1,
+ "tmax": 500
+ },
+ "httpCalls": [
+ {
+ "expectedRequest":
+ {
+ "uri": "http://search.nutella.datablocks.net/openrtb2?sid=123",
+ "body":
+ {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner":
+ {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "search.nutella.datablocks.net",
+ "sourceId": 123
+ }
+ }
+ }],
+ "site":
+ {
+ "page": "prebid.org"
+ },
+ "device":
+ {
+ "ip": "8.8.8.10"
+ },
+ "at": 1,
+ "tmax": 500
+ }
+ },
+ "mockResponse":
+ {
+ "status": 200,
+ "body":"foobar"
+ }
+ }],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/datablocks/datablockstest/supplemental/bad-server-response.json b/adapters/datablocks/datablockstest/supplemental/bad-server-response.json
new file mode 100644
index 00000000000..da2924f6f21
--- /dev/null
+++ b/adapters/datablocks/datablockstest/supplemental/bad-server-response.json
@@ -0,0 +1,88 @@
+{
+ "mockBidRequest":
+ {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner":
+ {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "search.nutella.datablocks.net",
+ "sourceId": 123
+ }
+ }
+ }],
+ "site":
+ {
+ "page": "prebid.org"
+ },
+ "device":
+ {
+ "ip": "8.8.8.10"
+ },
+ "at": 1,
+ "tmax": 500
+ },
+ "httpCalls": [
+ {
+ "expectedRequest":
+ {
+ "uri": "http://search.nutella.datablocks.net/openrtb2?sid=123",
+ "body":
+ {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner":
+ {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "search.nutella.datablocks.net",
+ "sourceId": 123
+ }
+ }
+ }],
+ "site":
+ {
+ "page": "prebid.org"
+ },
+ "device":
+ {
+ "ip": "8.8.8.10"
+ },
+ "at": 1,
+ "tmax": 500
+ }
+ },
+ "mockResponse":
+ {
+ "status": 500,
+ "body": {}
+ }
+ }],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "ERR, response with status 500",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/datablocks/datablockstest/supplemental/bad-sourceId.json b/adapters/datablocks/datablockstest/supplemental/bad-sourceId.json
new file mode 100644
index 00000000000..08f2fddf568
--- /dev/null
+++ b/adapters/datablocks/datablockstest/supplemental/bad-sourceId.json
@@ -0,0 +1,35 @@
+{
+ "mockBidRequest": {
+ "id": "bad-sourceId-test",
+ "imp": [
+ {
+ "id": "bad-sourceId-test-imp",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "search.nutella.datablocks.net",
+ "sourceId": 0
+ }
+ }
+ }
+ ]
+ },
+
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "Invalid/Missing SourceId",
+ "comparison": "literal"
+ }
+ ]
+
+
+}
diff --git a/adapters/datablocks/datablockstest/supplemental/missing-ext.json b/adapters/datablocks/datablockstest/supplemental/missing-ext.json
new file mode 100644
index 00000000000..68d29e880b9
--- /dev/null
+++ b/adapters/datablocks/datablockstest/supplemental/missing-ext.json
@@ -0,0 +1,27 @@
+{
+ "mockBidRequest": {
+ "id": "missing-extbid-test",
+ "imp": [
+ {
+ "id": "missing-extbid-test",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ }
+ }
+ ]
+ },
+
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "Missing bidder ext: unexpected end of JSON input",
+ "comparison": "literal"
+ }
+ ]
+
+
+}
diff --git a/adapters/datablocks/datablockstest/supplemental/missing-extparam.json b/adapters/datablocks/datablockstest/supplemental/missing-extparam.json
new file mode 100644
index 00000000000..d272cd5347c
--- /dev/null
+++ b/adapters/datablocks/datablockstest/supplemental/missing-extparam.json
@@ -0,0 +1,30 @@
+{
+ "mockBidRequest": {
+ "id": "missing-extbid-test",
+ "imp": [
+ {
+ "id": "missing-extbid-test",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "sourceId":54326
+ }
+ }
+ ]
+ },
+
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "Cannot Resolve host or sourceId: unexpected end of JSON input",
+ "comparison": "literal"
+ }
+ ]
+
+
+}
diff --git a/adapters/datablocks/datablockstest/supplemental/no-content-response.json b/adapters/datablocks/datablockstest/supplemental/no-content-response.json
new file mode 100644
index 00000000000..7fc1b75c3ad
--- /dev/null
+++ b/adapters/datablocks/datablockstest/supplemental/no-content-response.json
@@ -0,0 +1,82 @@
+{
+ "mockBidRequest":
+ {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner":
+ {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "search.nutella.datablocks.net",
+ "sourceId": 123
+ }
+ }
+ }],
+ "site":
+ {
+ "page": "prebid.org"
+ },
+ "device":
+ {
+ "ip": "8.8.8.10"
+ },
+ "at": 1,
+ "tmax": 500
+ },
+ "httpCalls": [
+ {
+ "expectedRequest":
+ {
+ "uri": "http://search.nutella.datablocks.net/openrtb2?sid=123",
+ "body":
+ {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "banner":
+ {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext":
+ {
+ "bidder":
+ {
+ "host": "search.nutella.datablocks.net",
+ "sourceId": 123
+ }
+ }
+ }],
+ "site":
+ {
+ "page": "prebid.org"
+ },
+ "device":
+ {
+ "ip": "8.8.8.10"
+ },
+ "at": 1,
+ "tmax": 500
+ }
+ },
+ "mockResponse":
+ {
+ "status": 204
+ }
+ }],
+ "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/datablocks/usersync.go b/adapters/datablocks/usersync.go
new file mode 100644
index 00000000000..c8ec92aa857
--- /dev/null
+++ b/adapters/datablocks/usersync.go
@@ -0,0 +1,14 @@
+package datablocks
+
+import (
+ "text/template"
+
+ "github.com/prebid/prebid-server/adapters"
+ "github.com/prebid/prebid-server/usersync"
+)
+
+const datablocksGDPRVendorID = uint16(14)
+
+func NewDatablocksSyncer(temp *template.Template) usersync.Usersyncer {
+ return adapters.NewSyncer("datablocks", 14, temp, adapters.SyncTypeRedirect)
+}
diff --git a/adapters/datablocks/usersync_test.go b/adapters/datablocks/usersync_test.go
new file mode 100644
index 00000000000..e31fff749fc
--- /dev/null
+++ b/adapters/datablocks/usersync_test.go
@@ -0,0 +1,18 @@
+package datablocks
+
+import (
+ "github.com/stretchr/testify/assert"
+ "testing"
+ "text/template"
+)
+
+func TestDatablocksSyncer(t *testing.T) {
+ temp := template.Must(template.New("sync-template").Parse("https://search.v5prebid.datablocks.net/s2ssync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3Ddatablocks%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D"))
+ syncer := NewDatablocksSyncer(temp)
+ syncInfo, err := syncer.GetUsersyncInfo("1", "BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw")
+ assert.NoError(t, err)
+ assert.Equal(t, "https://search.v5prebid.datablocks.net/s2ssync?gdpr=1&gdpr_consent=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw&r=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3Ddatablocks%26gdpr%3D1%26gdpr_consent%3DBONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw%26uid%3D%24%7Buid%7D", syncInfo.URL)
+ assert.Equal(t, "redirect", syncInfo.Type)
+ assert.EqualValues(t, datablocksGDPRVendorID, syncer.GDPRVendorID())
+ assert.Equal(t, false, syncInfo.SupportCORS)
+}
diff --git a/config/config.go b/config/config.go
index 90b38c62510..e2a8170bb32 100644
--- a/config/config.go
+++ b/config/config.go
@@ -481,6 +481,7 @@ func (cfg *Configuration) setDerivedDefaults() {
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBeachfront, "https://sync.bfmio.com/sync_s2s?gdpr={{.GDPR}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbeachfront%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Bio_cid%5D")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderConsumable, "https://e.serverbid.com/udb/9969/match?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dconsumable%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderConversant, "https://prebid-match.dotomi.com/prebid/match?rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dconversant%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D")
+ setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDatablocks, "https://search.v5prebid.datablocks.net/s2ssync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ddatablocks%26gdpr%3D%24%7Bgdpr%7D%26gdpr_consent%3D%24%7Bgdpr_consent%7D%26uid%3D%24%7Buid%7D")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEmxDigital, "https://cs.emxdgt.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Demx_digital%26uid%3D%24UID")
setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEPlanning, "https://ads.us.e-planning.net/uspd/1/?du=https%3A%2F%2Fads.us.e-planning.net%2Fgetuid%2F1%2F5a1ad71d2d53a0f5%3F"+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Deplanning%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID")
// openrtb_ext.BidderFacebook doesn't have a good default.
@@ -653,6 +654,7 @@ func SetupViper(v *viper.Viper, filename string) {
v.SetDefault("adapters.beachfront.endpoint", "https://display.bfmio.com/prebid_display")
v.SetDefault("adapters.brightroll.endpoint", "http://east-bid.ybp.yahoo.com/bid/appnexuspbs")
v.SetDefault("adapters.consumable.endpoint", "https://e.serverbid.com/api/v2")
+ v.SetDefault("adapters.datablocks.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}")
v.SetDefault("adapters.engagebdr.endpoint", "http://dsp.bnmla.com/hb")
v.SetDefault("adapters.conversant.endpoint", "http://api.hb.ad.cpe.dotomi.com/s2s/header/24")
v.SetDefault("adapters.emx_digital.endpoint", "https://hb.emxdgt.com")
diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go
index 88f6c40f727..a1806ef7f32 100644
--- a/exchange/adapter_map.go
+++ b/exchange/adapter_map.go
@@ -19,6 +19,7 @@ import (
"github.com/prebid/prebid-server/adapters/brightroll"
"github.com/prebid/prebid-server/adapters/consumable"
"github.com/prebid/prebid-server/adapters/conversant"
+ "github.com/prebid/prebid-server/adapters/datablocks"
"github.com/prebid/prebid-server/adapters/emx_digital"
"github.com/prebid/prebid-server/adapters/engagebdr"
"github.com/prebid/prebid-server/adapters/eplanning"
@@ -69,6 +70,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter
openrtb_ext.BidderBeachfront: beachfront.NewBeachfrontBidder(),
openrtb_ext.BidderBrightroll: brightroll.NewBrightrollBidder(cfg.Adapters[string(openrtb_ext.BidderBrightroll)].Endpoint),
openrtb_ext.BidderConsumable: consumable.NewConsumableBidder(cfg.Adapters[string(openrtb_ext.BidderConsumable)].Endpoint),
+ openrtb_ext.BidderDatablocks: datablocks.NewDatablocksBidder(cfg.Adapters[string(openrtb_ext.BidderDatablocks)].Endpoint),
openrtb_ext.BidderEngageBDR: engagebdr.NewEngageBDRBidder(client, cfg.Adapters[string(openrtb_ext.BidderEngageBDR)].Endpoint),
openrtb_ext.BidderEmxDigital: emx_digital.NewEmxDigitalBidder(cfg.Adapters[string(openrtb_ext.BidderEmxDigital)].Endpoint),
openrtb_ext.BidderEPlanning: eplanning.NewEPlanningBidder(client, cfg.Adapters[string(openrtb_ext.BidderEPlanning)].Endpoint),
diff --git a/macros/macros.go b/macros/macros.go
index 03da633b472..dd5832c35b9 100644
--- a/macros/macros.go
+++ b/macros/macros.go
@@ -10,6 +10,7 @@ type EndpointTemplateParams struct {
Host string
PublisherID string
ZoneID string
+ SourceId string
}
// UserSyncTemplateParams specifies params for an user sync URL template
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index d5f125777b4..1e185895380 100644
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -32,6 +32,7 @@ const (
BidderBrightroll BidderName = "brightroll"
BidderConsumable BidderName = "consumable"
BidderConversant BidderName = "conversant"
+ BidderDatablocks BidderName = "datablocks"
BidderEmxDigital BidderName = "emx_digital"
BidderEPlanning BidderName = "eplanning"
BidderFacebook BidderName = "audienceNetwork"
@@ -80,6 +81,7 @@ var BidderMap = map[string]BidderName{
"brightroll": BidderBrightroll,
"consumable": BidderConsumable,
"conversant": BidderConversant,
+ "datablocks": BidderDatablocks,
"emx_digital": BidderEmxDigital,
"eplanning": BidderEPlanning,
"gamma": BidderGamma,
diff --git a/openrtb_ext/imp_datablocks.go b/openrtb_ext/imp_datablocks.go
new file mode 100644
index 00000000000..fdb5dd398d4
--- /dev/null
+++ b/openrtb_ext/imp_datablocks.go
@@ -0,0 +1,7 @@
+package openrtb_ext
+
+// ExtImpDatablocks defines the contract for bidrequest.imp[i].ext.datablocks
+type ExtImpDatablocks struct {
+ SourceId int `json:"sourceId"`
+ Host string `json:"host"`
+}
diff --git a/static/bidder-info/datablocks.yaml b/static/bidder-info/datablocks.yaml
new file mode 100644
index 00000000000..9bf7e780914
--- /dev/null
+++ b/static/bidder-info/datablocks.yaml
@@ -0,0 +1,13 @@
+maintainer:
+ email: "henry@datablocks.net"
+capabilities:
+ app:
+ mediaTypes:
+ - banner
+ - native
+ - video
+ site:
+ mediaTypes:
+ - banner
+ - native
+ - video
diff --git a/static/bidder-params/datablocks.json b/static/bidder-params/datablocks.json
new file mode 100644
index 00000000000..2abb986786b
--- /dev/null
+++ b/static/bidder-params/datablocks.json
@@ -0,0 +1,19 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "Datablocks Adapter Params",
+ "description": "A schema which validates params accepted by the Datablocks adapter",
+
+ "type": "object",
+ "properties": {
+ "sourceId": {
+ "type": "integer",
+ "minimum": 1,
+ "description": "Website Source Id"
+ },
+ "host": {
+ "type": "string",
+ "description": "Network Host to request from"
+ }
+ },
+ "required": ["host", "sourceId"]
+}
diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go
index 28ea08a1ceb..250f5a62dae 100644
--- a/usersync/usersyncers/syncer.go
+++ b/usersync/usersyncers/syncer.go
@@ -18,6 +18,7 @@ import (
"github.com/prebid/prebid-server/adapters/brightroll"
"github.com/prebid/prebid-server/adapters/consumable"
"github.com/prebid/prebid-server/adapters/conversant"
+ "github.com/prebid/prebid-server/adapters/datablocks"
"github.com/prebid/prebid-server/adapters/emx_digital"
"github.com/prebid/prebid-server/adapters/engagebdr"
"github.com/prebid/prebid-server/adapters/eplanning"
@@ -70,6 +71,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync
insertIntoMap(cfg, syncers, openrtb_ext.BidderBrightroll, brightroll.NewBrightrollSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderConsumable, consumable.NewConsumableSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderConversant, conversant.NewConversantSyncer)
+ insertIntoMap(cfg, syncers, openrtb_ext.BidderDatablocks, datablocks.NewDatablocksSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderEmxDigital, emx_digital.NewEMXDigitalSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderEPlanning, eplanning.NewEPlanningSyncer)
insertIntoMap(cfg, syncers, openrtb_ext.BidderFacebook, audienceNetwork.NewFacebookSyncer)
diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go
index 7a7e33648fd..f1452d3373b 100644
--- a/usersync/usersyncers/syncer_test.go
+++ b/usersync/usersyncers/syncer_test.go
@@ -26,6 +26,7 @@ func TestNewSyncerMap(t *testing.T) {
string(openrtb_ext.BidderBrightroll): syncConfig,
string(openrtb_ext.BidderConsumable): syncConfig,
string(openrtb_ext.BidderConversant): syncConfig,
+ string(openrtb_ext.BidderDatablocks): syncConfig,
string(openrtb_ext.BidderEmxDigital): syncConfig,
string(openrtb_ext.BidderEPlanning): syncConfig,
string(openrtb_ext.BidderGrid): syncConfig,