Skip to content

Commit e48a2ee

Browse files
authored
Merge branch 'prebid:master' into feature/mocktioneer
2 parents e6ea36d + 85f6a6b commit e48a2ee

93 files changed

Lines changed: 6257 additions & 348 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

adapters/goldbach/goldbach.go

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
package goldbach
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
7+
"github.com/prebid/openrtb/v20/openrtb2"
8+
"github.com/prebid/prebid-server/v3/adapters"
9+
"github.com/prebid/prebid-server/v3/config"
10+
"github.com/prebid/prebid-server/v3/errortypes"
11+
"github.com/prebid/prebid-server/v3/openrtb_ext"
12+
"github.com/prebid/prebid-server/v3/util/jsonutil"
13+
)
14+
15+
type adapter struct {
16+
endpoint string
17+
}
18+
19+
type requestExtAdapter struct {
20+
Goldbach requestExtGoldbach `json:"goldbach"`
21+
22+
*openrtb_ext.ExtRequest `json:"inline,omitempty"`
23+
}
24+
25+
type requestExtGoldbach struct {
26+
PublisherID string `json:"publisherId"`
27+
MockResponse *bool `json:"mockResponse,omitempty"`
28+
}
29+
30+
type impExtAdapter struct {
31+
Goldbach impExtGoldbachOutgoing `json:"goldbach"`
32+
}
33+
34+
type impExtGoldbachOutgoing struct {
35+
Targetings map[string][]string `json:"targetings,omitempty"`
36+
SlotID string `json:"slotId"`
37+
}
38+
39+
func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
40+
bidder := &adapter{
41+
endpoint: config.Endpoint,
42+
}
43+
return bidder, nil
44+
}
45+
46+
func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
47+
var reqs []*adapters.RequestData
48+
var errs []error
49+
50+
var requestExt requestExtAdapter
51+
if request.Ext != nil {
52+
if err := jsonutil.Unmarshal(request.Ext, &requestExt); err != nil {
53+
errs = append(errs, &errortypes.FailedToUnmarshal{Message: fmt.Errorf("unable to unmarshal request.ext: %w", err).Error()})
54+
}
55+
}
56+
57+
// group impressions by publisher ID
58+
publisherImps := make(map[string][]openrtb2.Imp)
59+
for _, imp := range request.Imp {
60+
publisherID, impCopy, err := buildImp(imp)
61+
if err != nil {
62+
errs = append(errs, err)
63+
continue
64+
}
65+
66+
publisherImps[publisherID] = append(publisherImps[publisherID], impCopy)
67+
}
68+
69+
if len(publisherImps) == 0 {
70+
errs = append(errs, &errortypes.BadInput{Message: "no valid impression found"})
71+
}
72+
73+
// create a separate request for each publisher
74+
for publisherID, imps := range publisherImps {
75+
requestPublisher, err := buildRequest(*request, publisherID, imps, &requestExt)
76+
if err != nil {
77+
errs = append(errs, err)
78+
continue
79+
}
80+
81+
resJSON, err := jsonutil.Marshal(&requestPublisher)
82+
if err != nil {
83+
errs = append(errs, &errortypes.FailedToMarshal{Message: fmt.Errorf("unable to marshal request: %w", err).Error()})
84+
continue
85+
}
86+
87+
headers := http.Header{}
88+
headers.Add("Content-Type", "application/json;charset=utf-8")
89+
headers.Add("Accept", "application/json")
90+
91+
req := &adapters.RequestData{
92+
Method: "POST",
93+
Uri: a.endpoint,
94+
Body: resJSON,
95+
Headers: headers,
96+
ImpIDs: openrtb_ext.GetImpIDs(requestPublisher.Imp),
97+
}
98+
99+
reqs = append(reqs, req)
100+
}
101+
102+
return reqs, errs
103+
}
104+
105+
func (a *adapter) MakeBids(bidReq *openrtb2.BidRequest, unused *adapters.RequestData, httpRes *adapters.ResponseData) (*adapters.BidderResponse, []error) {
106+
if httpRes.StatusCode == http.StatusNoContent {
107+
return nil, nil
108+
}
109+
110+
if httpRes.StatusCode != http.StatusCreated {
111+
return nil, []error{&errortypes.BadServerResponse{
112+
Message: fmt.Sprintf("unexpected status code: %d. Run with request.debug = 1 for more info", httpRes.StatusCode),
113+
}}
114+
}
115+
116+
var resp openrtb2.BidResponse
117+
if err := jsonutil.Unmarshal(httpRes.Body, &resp); err != nil {
118+
return nil, []error{&errortypes.BadServerResponse{
119+
Message: fmt.Errorf("unable to unmarshal response: %w", err).Error(),
120+
}}
121+
}
122+
123+
bidderResponse := adapters.NewBidderResponse()
124+
bidderResponse.Currency = resp.Cur
125+
126+
var errs []error
127+
for _, sb := range resp.SeatBid {
128+
for i := range sb.Bid {
129+
bidType, err := getBidMediaType(&sb.Bid[i])
130+
if err != nil {
131+
errs = append(errs, err)
132+
continue
133+
}
134+
135+
bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{
136+
Bid: &sb.Bid[i],
137+
BidType: bidType,
138+
})
139+
}
140+
}
141+
142+
if len(bidderResponse.Bids) == 0 {
143+
errs = append(errs, &errortypes.BadServerResponse{
144+
Message: "no valid bids found in response",
145+
})
146+
return nil, errs
147+
}
148+
149+
return bidderResponse, errs
150+
}
151+
152+
func buildImp(imp openrtb2.Imp) (string, openrtb2.Imp, error) {
153+
impExt, err := extractImpExt(&imp)
154+
if err != nil {
155+
return "", openrtb2.Imp{}, err
156+
}
157+
158+
targetings := make(map[string][]string)
159+
for key, value := range impExt.CustomTargeting {
160+
targetings[key] = value
161+
}
162+
163+
imp.Ext, err = jsonutil.Marshal(&impExtAdapter{
164+
Goldbach: impExtGoldbachOutgoing{
165+
Targetings: targetings,
166+
SlotID: impExt.SlotID,
167+
},
168+
})
169+
170+
if err != nil {
171+
return "", openrtb2.Imp{}, &errortypes.FailedToMarshal{Message: fmt.Errorf("unable to marshal imp.ext: %w", err).Error()}
172+
}
173+
174+
return impExt.PublisherID, imp, nil
175+
}
176+
177+
func extractImpExt(imp *openrtb2.Imp) (*openrtb_ext.ImpExtGoldbach, error) {
178+
var extImpBidder adapters.ExtImpBidder
179+
if err := jsonutil.Unmarshal(imp.Ext, &extImpBidder); err != nil {
180+
return nil, &errortypes.BadInput{
181+
Message: fmt.Errorf("unable to unmarshal imp.ext: %w", err).Error(),
182+
}
183+
}
184+
185+
var goldbachExt openrtb_ext.ImpExtGoldbach
186+
if err := jsonutil.Unmarshal(extImpBidder.Bidder, &goldbachExt); err != nil {
187+
return nil, &errortypes.BadInput{
188+
Message: fmt.Errorf("unable to unmarshal imp.ext.bidder: %w", err).Error(),
189+
}
190+
}
191+
192+
if len(goldbachExt.PublisherID) == 0 || len(goldbachExt.SlotID) == 0 {
193+
return nil, &errortypes.BadInput{
194+
Message: "publisherId and slotId are required",
195+
}
196+
}
197+
return &goldbachExt, nil
198+
}
199+
200+
func buildRequest(request openrtb2.BidRequest, publisherID string, imps []openrtb2.Imp, requestExt *requestExtAdapter) (*openrtb2.BidRequest, error) {
201+
request.Imp = imps
202+
request.ID = fmt.Sprintf("%s_%s", request.ID, publisherID)
203+
204+
// Set the publisher ID in the request.ext
205+
requestPublisherExt, err := jsonutil.Marshal(&requestExtAdapter{
206+
Goldbach: requestExtGoldbach{
207+
PublisherID: publisherID,
208+
MockResponse: requestExt.Goldbach.MockResponse,
209+
},
210+
ExtRequest: requestExt.ExtRequest,
211+
})
212+
if err != nil {
213+
return nil, &errortypes.FailedToMarshal{Message: fmt.Errorf("unable to marshal request.ext: %w", err).Error()}
214+
}
215+
216+
request.Ext = requestPublisherExt
217+
218+
return &request, nil
219+
}
220+
221+
func getBidMediaType(bid *openrtb2.Bid) (openrtb_ext.BidType, error) {
222+
var extBid openrtb_ext.ExtBid
223+
if err := jsonutil.Unmarshal(bid.Ext, &extBid); err != nil {
224+
return "", &errortypes.FailedToUnmarshal{Message: fmt.Errorf("unable to unmarshal ext for bid: %w", err).Error()}
225+
}
226+
227+
if extBid.Prebid == nil || len(extBid.Prebid.Type) == 0 {
228+
return "", &errortypes.BadInput{Message: fmt.Sprintf("no media type for bid %v", bid.ID)}
229+
}
230+
231+
return extBid.Prebid.Type, nil
232+
}

adapters/goldbach/goldbach_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package goldbach
2+
3+
import (
4+
"testing"
5+
6+
"github.com/prebid/prebid-server/v3/adapters/adapterstest"
7+
"github.com/prebid/prebid-server/v3/config"
8+
"github.com/prebid/prebid-server/v3/openrtb_ext"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestJsonSamples(t *testing.T) {
13+
bidder, buildErr := Builder(
14+
openrtb_ext.BidderGoldbach,
15+
config.Adapter{
16+
Endpoint: "https://gold.bach/prebid/adapter-endpoint",
17+
},
18+
config.Server{
19+
ExternalUrl: "http://hosturl.com",
20+
GvlID: 1,
21+
DataCenter: "2",
22+
},
23+
)
24+
25+
require.NoError(t, buildErr)
26+
27+
adapterstest.RunJSONBidderTest(t, "goldbachtest", bidder)
28+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
{
2+
"mockBidRequest": {
3+
"imp": [
4+
{
5+
"id": "test-imp-123",
6+
"banner": {
7+
"h": 250,
8+
"w": 300,
9+
"pos": 0
10+
},
11+
"ext": {
12+
"bidder": {
13+
"slotId": "12345678/de-example.ch/slot-id/some-page",
14+
"publisherId": "de-example.ch",
15+
"customTargeting": {
16+
"key1": "value1",
17+
"key2": ["value2", "value3"]
18+
}
19+
}
20+
}
21+
}
22+
],
23+
"site": {
24+
"page": "https://www.example.ch/"
25+
},
26+
"device": {
27+
"ua": "Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Mobile Safari/537.36"
28+
},
29+
"ext": {},
30+
"id": "723f8906-7f59-4f50-bba9-ed9115ab1663"
31+
},
32+
"httpCalls": [
33+
{
34+
"expectedRequest": {
35+
"headers": {
36+
"Accept": ["application/json"],
37+
"Content-Type": ["application/json;charset=utf-8"]
38+
},
39+
"uri": "https://gold.bach/prebid/adapter-endpoint",
40+
"body": {
41+
"imp": [
42+
{
43+
"ext": {
44+
"goldbach": {
45+
"slotId": "12345678/de-example.ch/slot-id/some-page",
46+
"targetings": {
47+
"key1": ["value1"],
48+
"key2": ["value2", "value3"]
49+
}
50+
}
51+
},
52+
"id": "test-imp-123",
53+
"banner": {
54+
"h": 250,
55+
"w": 300,
56+
"pos": 0
57+
}
58+
}
59+
],
60+
"site": {
61+
"page": "https://www.example.ch/"
62+
},
63+
"device": {
64+
"ua": "Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Mobile Safari/537.36"
65+
},
66+
"ext": {
67+
"goldbach": {
68+
"publisherId": "de-example.ch"
69+
}
70+
},
71+
"id": "723f8906-7f59-4f50-bba9-ed9115ab1663_de-example.ch"
72+
},
73+
"impIDs": ["test-imp-123"]
74+
},
75+
"mockResponse": {
76+
"status": 201,
77+
"headers": {},
78+
"body": {
79+
"id": "c04f2011-87e9-4cde-9152-78fa2a63078a",
80+
"seatbid": [
81+
{
82+
"bid": [
83+
{
84+
"id": "b334cb75-41d8-4f61-801c-1e785a2fe38d",
85+
"price": 2.5,
86+
"adm": "test-ad-content",
87+
"adid": "456",
88+
"impid": "test-imp-123",
89+
"crid": "1234",
90+
"cid": "1234",
91+
"ext": {
92+
"prebid": {
93+
"type": "banner"
94+
}
95+
}
96+
}
97+
]
98+
}
99+
]
100+
}
101+
}
102+
}
103+
],
104+
"expectedBidResponses": [
105+
{
106+
"currency": "CHF",
107+
"bids": [
108+
{
109+
"bid": {
110+
"id": "b334cb75-41d8-4f61-801c-1e785a2fe38d",
111+
"price": 2.5,
112+
"adm": "test-ad-content",
113+
"adid": "456",
114+
"impid": "test-imp-123",
115+
"crid": "1234",
116+
"cid": "1234",
117+
"ext": {
118+
"prebid": {
119+
"type": "banner"
120+
}
121+
}
122+
},
123+
"type": "banner"
124+
}
125+
]
126+
}
127+
]
128+
}

0 commit comments

Comments
 (0)