Skip to content

Commit f27bcef

Browse files
authored
New Adapter: Bidmatic (#3731)
authored by @dkornet-ad
1 parent 6c154e0 commit f27bcef

File tree

16 files changed

+852
-0
lines changed

16 files changed

+852
-0
lines changed

adapters/bidmatic/bidmatic.go

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
package bidmatic
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/http"
7+
8+
"github.com/prebid/openrtb/v20/openrtb2"
9+
"github.com/prebid/prebid-server/v2/adapters"
10+
"github.com/prebid/prebid-server/v2/config"
11+
"github.com/prebid/prebid-server/v2/errortypes"
12+
"github.com/prebid/prebid-server/v2/openrtb_ext"
13+
)
14+
15+
type adapter struct {
16+
endpoint string
17+
}
18+
19+
type bidmaticImpExt struct {
20+
Bidmatic openrtb_ext.ExtImpBidmatic `json:"bidmatic"`
21+
}
22+
23+
func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
24+
totalImps := len(request.Imp)
25+
errors := make([]error, 0, totalImps)
26+
imp2source := make(map[int][]int)
27+
28+
for i := 0; i < totalImps; i++ {
29+
sourceId, err := validateImpression(&request.Imp[i])
30+
if err != nil {
31+
errors = append(errors, err)
32+
continue
33+
}
34+
35+
if _, ok := imp2source[sourceId]; !ok {
36+
imp2source[sourceId] = make([]int, 0, totalImps-i)
37+
}
38+
39+
imp2source[sourceId] = append(imp2source[sourceId], i)
40+
}
41+
42+
totalReqs := len(imp2source)
43+
if totalReqs == 0 {
44+
return nil, errors
45+
}
46+
47+
headers := http.Header{}
48+
headers.Add("Content-Type", "application/json;charset=utf-8")
49+
headers.Add("Accept", "application/json")
50+
51+
reqs := make([]*adapters.RequestData, 0, totalReqs)
52+
53+
imps := request.Imp
54+
request.Imp = make([]openrtb2.Imp, 0, len(imps))
55+
for sourceId, impIds := range imp2source {
56+
request.Imp = request.Imp[:0]
57+
58+
for i := 0; i < len(impIds); i++ {
59+
request.Imp = append(request.Imp, imps[impIds[i]])
60+
}
61+
62+
body, err := json.Marshal(request)
63+
if err != nil {
64+
errors = append(errors, fmt.Errorf("error while encoding bidRequest, err: %s", err))
65+
return nil, errors
66+
}
67+
68+
reqs = append(reqs, &adapters.RequestData{
69+
Method: "POST",
70+
Uri: a.endpoint + fmt.Sprintf("?source=%d", sourceId),
71+
Body: body,
72+
Headers: headers,
73+
ImpIDs: openrtb_ext.GetImpIDs(request.Imp),
74+
})
75+
}
76+
77+
if len(reqs) == 0 {
78+
return nil, errors
79+
}
80+
81+
return reqs, errors
82+
}
83+
84+
func (a *adapter) MakeBids(bidReq *openrtb2.BidRequest, unused *adapters.RequestData, httpRes *adapters.ResponseData) (*adapters.BidderResponse, []error) {
85+
if adapters.IsResponseStatusCodeNoContent(httpRes) {
86+
return nil, nil
87+
}
88+
if err := adapters.CheckResponseStatusCodeForErrors(httpRes); err != nil {
89+
return nil, []error{err}
90+
}
91+
92+
var bidResp openrtb2.BidResponse
93+
if err := json.Unmarshal(httpRes.Body, &bidResp); err != nil {
94+
return nil, []error{&errortypes.BadServerResponse{
95+
Message: fmt.Sprintf("error while decoding response, err: %s", err),
96+
}}
97+
}
98+
99+
bidResponse := adapters.NewBidderResponse()
100+
var errors []error
101+
102+
var impOK bool
103+
for _, sb := range bidResp.SeatBid {
104+
for i := 0; i < len(sb.Bid); i++ {
105+
106+
bid := sb.Bid[i]
107+
108+
impOK = false
109+
mediaType := openrtb_ext.BidTypeBanner
110+
bid.MType = openrtb2.MarkupBanner
111+
loop:
112+
for _, imp := range bidReq.Imp {
113+
if imp.ID == bid.ImpID {
114+
115+
impOK = true
116+
117+
switch {
118+
case imp.Video != nil:
119+
mediaType = openrtb_ext.BidTypeVideo
120+
bid.MType = openrtb2.MarkupVideo
121+
break loop
122+
case imp.Banner != nil:
123+
mediaType = openrtb_ext.BidTypeBanner
124+
bid.MType = openrtb2.MarkupBanner
125+
break loop
126+
case imp.Audio != nil:
127+
mediaType = openrtb_ext.BidTypeAudio
128+
bid.MType = openrtb2.MarkupAudio
129+
break loop
130+
case imp.Native != nil:
131+
mediaType = openrtb_ext.BidTypeNative
132+
bid.MType = openrtb2.MarkupNative
133+
break loop
134+
}
135+
}
136+
}
137+
138+
if !impOK {
139+
errors = append(errors, &errortypes.BadServerResponse{
140+
Message: fmt.Sprintf("ignoring bid id=%s, request doesn't contain any impression with id=%s", bid.ID, bid.ImpID),
141+
})
142+
continue
143+
}
144+
145+
bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
146+
Bid: &bid,
147+
BidType: mediaType,
148+
})
149+
}
150+
}
151+
152+
return bidResponse, errors
153+
}
154+
155+
func validateImpression(imp *openrtb2.Imp) (int, error) {
156+
if len(imp.Ext) == 0 {
157+
return 0, &errortypes.BadInput{
158+
Message: fmt.Sprintf("ignoring imp id=%s, extImpBidder is empty", imp.ID),
159+
}
160+
}
161+
162+
var bidderExt adapters.ExtImpBidder
163+
164+
if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
165+
return 0, &errortypes.BadInput{
166+
Message: fmt.Sprintf("ignoring imp id=%s, error while decoding extImpBidder, err: %s", imp.ID, err),
167+
}
168+
}
169+
170+
impExt := openrtb_ext.ExtImpBidmatic{}
171+
err := json.Unmarshal(bidderExt.Bidder, &impExt)
172+
if err != nil {
173+
return 0, &errortypes.BadInput{
174+
Message: fmt.Sprintf("ignoring imp id=%s, error while decoding impExt, err: %s", imp.ID, err),
175+
}
176+
}
177+
178+
// common extension for all impressions
179+
var impExtBuffer []byte
180+
181+
impExtBuffer, err = json.Marshal(&bidmaticImpExt{
182+
Bidmatic: impExt,
183+
})
184+
if err != nil {
185+
return 0, &errortypes.BadInput{
186+
Message: fmt.Sprintf("ignoring imp id=%s, error while marshaling impExt, err: %s", imp.ID, err),
187+
}
188+
}
189+
190+
if impExt.BidFloor > 0 {
191+
imp.BidFloor = impExt.BidFloor
192+
}
193+
194+
imp.Ext = impExtBuffer
195+
196+
source, err := impExt.SourceId.Int64() // json.Unmarshal returns err if it isn't valid
197+
if err != nil {
198+
return 0, err
199+
}
200+
return int(source), nil
201+
}
202+
203+
// Builder builds a new instance of the bidmatic adapter for the given bidder with the given config.
204+
func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
205+
return &adapter{endpoint: config.Endpoint}, nil
206+
}

adapters/bidmatic/bidmatic_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package bidmatic
2+
3+
import (
4+
"testing"
5+
6+
"github.com/prebid/prebid-server/v2/adapters/adapterstest"
7+
"github.com/prebid/prebid-server/v2/config"
8+
"github.com/prebid/prebid-server/v2/openrtb_ext"
9+
)
10+
11+
func TestJsonSamples(t *testing.T) {
12+
bidder, buildErr := Builder(
13+
openrtb_ext.BidderBidmatic,
14+
config.Adapter{Endpoint: "http://adapter.bidmatic.io/pbs/ortb"},
15+
config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"},
16+
)
17+
18+
if buildErr != nil {
19+
t.Fatalf("Builder returned unexpected error %v", buildErr)
20+
}
21+
22+
adapterstest.RunJSONBidderTest(t, "bidmatictest", bidder)
23+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
{
2+
"mockBidRequest": {
3+
"id": "test-request-id",
4+
"imp": [
5+
{
6+
"id": "test-imp-id",
7+
"video": {
8+
"w": 900,
9+
"h": 250,
10+
"mimes": [
11+
"video/x-flv",
12+
"video/mp4"
13+
]
14+
},
15+
"ext": {
16+
"bidder": {
17+
"source": 1000
18+
}
19+
}
20+
}
21+
]
22+
},
23+
24+
"httpCalls": [
25+
{
26+
"expectedRequest": {
27+
"uri": "http://adapter.bidmatic.io/pbs/ortb?source=1000",
28+
"body": {
29+
"id": "test-request-id",
30+
"imp": [
31+
{
32+
"id":"test-imp-id",
33+
"video": {
34+
"w": 900,
35+
"h": 250,
36+
"mimes": [
37+
"video/x-flv",
38+
"video/mp4"
39+
]
40+
},
41+
"ext": {
42+
"bidmatic": {
43+
"source": 1000
44+
}
45+
}
46+
}
47+
]
48+
},
49+
"impIDs":["test-imp-id"]
50+
},
51+
"mockResponse": {
52+
"status": 200,
53+
"body": {
54+
"id": "test-request-id",
55+
"seatbid": [
56+
{
57+
"bid": [
58+
{
59+
"id": "test-bid-id",
60+
"impid": "test-imp-id",
61+
"mtype": 2,
62+
"price": 3.5,
63+
"w": 900,
64+
"h": 250
65+
}
66+
]
67+
}
68+
]
69+
}
70+
}
71+
}
72+
],
73+
"expectedBidResponses": [
74+
{
75+
"currency": "USD",
76+
"bids": [
77+
{
78+
"bid": {
79+
"id": "test-bid-id",
80+
"impid": "test-imp-id",
81+
"mtype": 2,
82+
"price": 3.5,
83+
"w": 900,
84+
"h": 250
85+
},
86+
"type": "video"
87+
}
88+
]
89+
}
90+
]
91+
}

0 commit comments

Comments
 (0)