This repository has been archived by the owner on Dec 22, 2022. It is now read-only.
forked from prebid/prebid-server
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Adoppler bidder support. (prebid#1186)
* Add Adoppler bidder support. * Address code review comments. Use JSON-templates for testing. * Fix misprint; Add url.PathEscape call for adunit URL parameter.
- Loading branch information
1 parent
fba3ee7
commit 53bb32f
Showing
18 changed files
with
485 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
package adoppler | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"net/http" | ||
"net/url" | ||
|
||
"github.com/mxmCherry/openrtb" | ||
"github.com/prebid/prebid-server/adapters" | ||
"github.com/prebid/prebid-server/errortypes" | ||
"github.com/prebid/prebid-server/openrtb_ext" | ||
) | ||
|
||
var bidHeaders http.Header = map[string][]string{ | ||
"Accept": {"application/json"}, | ||
"Content-Type": {"application/json;charset=utf-8"}, | ||
"X-OpenRTB-Version": {"2.5"}, | ||
} | ||
|
||
type adsVideoExt struct { | ||
Duration int `json:"duration"` | ||
} | ||
|
||
type adsImpExt struct { | ||
Video *adsVideoExt `json:"video"` | ||
} | ||
|
||
type AdopplerAdapter struct { | ||
endpoint string | ||
} | ||
|
||
func NewAdopplerBidder(endpoint string) *AdopplerAdapter { | ||
return &AdopplerAdapter{endpoint} | ||
} | ||
|
||
func (ads *AdopplerAdapter) MakeRequests( | ||
req *openrtb.BidRequest, | ||
info *adapters.ExtraRequestInfo, | ||
) ( | ||
[]*adapters.RequestData, | ||
[]error, | ||
) { | ||
if len(req.Imp) == 0 { | ||
return nil, nil | ||
} | ||
|
||
var datas []*adapters.RequestData | ||
var errs []error | ||
for _, imp := range req.Imp { | ||
ext, err := unmarshalExt(imp.Ext) | ||
if err != nil { | ||
errs = append(errs, &errortypes.BadInput{err.Error()}) | ||
continue | ||
} | ||
|
||
var r openrtb.BidRequest = *req | ||
r.ID = req.ID + "-" + ext.AdUnit | ||
r.Imp = []openrtb.Imp{imp} | ||
|
||
body, err := json.Marshal(r) | ||
if err != nil { | ||
errs = append(errs, err) | ||
continue | ||
} | ||
|
||
uri := fmt.Sprintf("%s/processHeaderBid/%s", | ||
ads.endpoint, url.PathEscape(ext.AdUnit)) | ||
data := &adapters.RequestData{ | ||
Method: "POST", | ||
Uri: uri, | ||
Body: body, | ||
Headers: bidHeaders, | ||
} | ||
datas = append(datas, data) | ||
} | ||
|
||
return datas, errs | ||
} | ||
|
||
func (ads *AdopplerAdapter) MakeBids( | ||
intReq *openrtb.BidRequest, | ||
extReq *adapters.RequestData, | ||
resp *adapters.ResponseData, | ||
) ( | ||
*adapters.BidderResponse, | ||
[]error, | ||
) { | ||
if resp.StatusCode == http.StatusNoContent { | ||
return nil, nil | ||
} | ||
if resp.StatusCode == http.StatusBadRequest { | ||
return nil, []error{&errortypes.BadInput{"bad request"}} | ||
} | ||
if resp.StatusCode != http.StatusOK { | ||
err := &errortypes.BadServerResponse{ | ||
fmt.Sprintf("unexpected status: %d", resp.StatusCode), | ||
} | ||
return nil, []error{err} | ||
} | ||
|
||
var bidResp openrtb.BidResponse | ||
err := json.Unmarshal(resp.Body, &bidResp) | ||
if err != nil { | ||
err := &errortypes.BadServerResponse{ | ||
fmt.Sprintf("invalid body: %s", err.Error()), | ||
} | ||
return nil, []error{err} | ||
} | ||
|
||
impTypes := make(map[string]openrtb_ext.BidType) | ||
for _, imp := range intReq.Imp { | ||
if _, ok := impTypes[imp.ID]; ok { | ||
return nil, []error{&errortypes.BadInput{ | ||
fmt.Sprintf("duplicate $.imp.id %s", imp.ID), | ||
}} | ||
} | ||
if imp.Banner != nil { | ||
impTypes[imp.ID] = openrtb_ext.BidTypeBanner | ||
} else if imp.Video != nil { | ||
impTypes[imp.ID] = openrtb_ext.BidTypeVideo | ||
} else if imp.Audio != nil { | ||
impTypes[imp.ID] = openrtb_ext.BidTypeAudio | ||
} else if imp.Native != nil { | ||
impTypes[imp.ID] = openrtb_ext.BidTypeNative | ||
} else { | ||
return nil, []error{&errortypes.BadInput{ | ||
"one of $.imp.banner, $.imp.video, $.imp.audio and $.imp.native field required", | ||
}} | ||
} | ||
} | ||
|
||
var bids []*adapters.TypedBid | ||
for _, seatBid := range bidResp.SeatBid { | ||
for _, bid := range seatBid.Bid { | ||
tp, ok := impTypes[bid.ImpID] | ||
if !ok { | ||
err := &errortypes.BadServerResponse{ | ||
fmt.Sprintf("unknown impid: %s", bid.ImpID), | ||
} | ||
return nil, []error{err} | ||
} | ||
|
||
var bidVideo *openrtb_ext.ExtBidPrebidVideo | ||
if tp == openrtb_ext.BidTypeVideo { | ||
adsExt, err := unmarshalAdsExt(bid.Ext) | ||
if err != nil { | ||
return nil, []error{&errortypes.BadServerResponse{err.Error()}} | ||
} | ||
if adsExt == nil || adsExt.Video == nil { | ||
return nil, []error{&errortypes.BadServerResponse{ | ||
"$.seatbid.bid.ext.ads.video required", | ||
}} | ||
} | ||
bidVideo = &openrtb_ext.ExtBidPrebidVideo{ | ||
Duration: adsExt.Video.Duration, | ||
PrimaryCategory: head(bid.Cat), | ||
} | ||
} | ||
bids = append(bids, &adapters.TypedBid{ | ||
Bid: &bid, | ||
BidType: tp, | ||
BidVideo: bidVideo, | ||
}) | ||
} | ||
} | ||
|
||
adsResp := adapters.NewBidderResponseWithBidsCapacity(len(bids)) | ||
adsResp.Bids = bids | ||
|
||
return adsResp, nil | ||
} | ||
|
||
func unmarshalExt(ext json.RawMessage) (*openrtb_ext.ExtImpAdoppler, error) { | ||
var bext adapters.ExtImpBidder | ||
err := json.Unmarshal(ext, &bext) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var adsExt openrtb_ext.ExtImpAdoppler | ||
err = json.Unmarshal(bext.Bidder, &adsExt) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if adsExt.AdUnit == "" { | ||
return nil, errors.New("$.imp.ext.adoppler.adunit required") | ||
} | ||
|
||
return &adsExt, nil | ||
} | ||
|
||
func unmarshalAdsExt(ext json.RawMessage) (*adsImpExt, error) { | ||
var e struct { | ||
Ads *adsImpExt `json:"ads"` | ||
} | ||
err := json.Unmarshal(ext, &e) | ||
|
||
return e.Ads, err | ||
} | ||
|
||
func head(s []string) string { | ||
if len(s) == 0 { | ||
return "" | ||
} | ||
|
||
return s[0] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package adoppler | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/prebid/prebid-server/adapters/adapterstest" | ||
) | ||
|
||
func TestJsonSamples(t *testing.T) { | ||
bidder := NewAdopplerBidder("http://adoppler.com") | ||
adapterstest.RunJSONBidderTest(t, "adopplertest", bidder) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
{"mockBidRequest": {"id": "req1", | ||
"imp":[{"id": "imp1", | ||
"banner": {"w": 100, | ||
"h": 200}, | ||
"ext": {"bidder": {"adunit": "unit1"}}}, | ||
{"id": "imp2", | ||
"video": {"minduration": 120, | ||
"mimes": ["video/mp4"]}, | ||
"ext": {"bidder": {"adunit": "unit2"}}}, | ||
{"id": "imp3", | ||
"native": {"request": "{}"}, | ||
"ext": {"bidder": {"adunit": "unit3"}}}]}, | ||
"httpCalls": [{"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit1", | ||
"body": {"id": "req1-unit1", | ||
"imp": [{"id": "imp1", | ||
"banner": {"w": 100, "h": 200}, | ||
"ext": {"bidder": {"adunit": "unit1"}}}]}}, | ||
"mockResponse": {"status": 200, | ||
"body": {"id": "req1-imp1-resp1", | ||
"seatbid": [{"bid": [{"id": "req1-imp1-bid1", | ||
"impid": "imp1", | ||
"price": 0.12, | ||
"adm": "<b>a banner</b>"}]}], | ||
"cur": "USD"}}}, | ||
{"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit2", | ||
"body": {"id": "req1-unit2", | ||
"imp": [{"id": "imp2", | ||
"video": {"minduration": 120, | ||
"mimes": ["video/mp4"]}, | ||
"ext": {"bidder": {"adunit": "unit2"}}}]}}, | ||
"mockResponse": {"status": 200, | ||
"body": {"id": "req1-imp2-resp2", | ||
"seatbid": [{"bid": [{"id": "req1-imp2-bid1", | ||
"impid": "imp2", | ||
"price": 0.24, | ||
"adm": "<VAST />", | ||
"cat": ["IAB1", "IAB2"], | ||
"ext": {"ads": {"video": {"duration": 121}}}}]}], | ||
"cur": "USD"}}}, | ||
{"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit3", | ||
"body": {"id": "req1-unit3", | ||
"imp": [{"id": "imp3", | ||
"native": {"request": "{}"}, | ||
"ext": {"bidder": {"adunit": "unit3"}}}]}}, | ||
"mockResponse": {"status": 204, | ||
"body": ""}}], | ||
"expectedBidResponses": [{"currency": "USD", | ||
"bids": [{"bid": {"id": "req1-imp1-bid1", | ||
"impid": "imp1", | ||
"price": 0.12, | ||
"adm": "<b>a banner</b>"}, | ||
"type": "banner"}]}, | ||
{"currency": "USD", | ||
"bids": [{"bid": {"id": "req1-imp2-bid1", | ||
"impid": "imp2", | ||
"price": 0.24, | ||
"adm": "<VAST />", | ||
"cat": ["IAB1", "IAB2"], | ||
"ext": {"ads": {"video": {"duration": 121}}}}, | ||
"type": "video"}]}]} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{"mockBidRequest": {"id": "req1", | ||
"imp":[{"id": "imp1", | ||
"banner": {"w": 100, | ||
"h": 200}, | ||
"ext": {"bidder": {"adunit": "unit1"}}}]}, | ||
"httpCalls": [{"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit1", | ||
"body": {"id": "req1-unit1", | ||
"imp": [{"id": "imp1", | ||
"banner": {"w": 100, "h": 200}, | ||
"ext": {"bidder": {"adunit": "unit1"}}}]}}, | ||
"mockResponse": {"status": 204, | ||
"body": ""}}], | ||
"expectedBidResponses": []} |
15 changes: 15 additions & 0 deletions
15
adapters/adoppler/adopplertest/supplemental/bad-request.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{"mockBidRequest": {"id": "req1", | ||
"imp":[{"id": "imp1", | ||
"banner": {"w": 100, | ||
"h": 200}, | ||
"ext": {"bidder": {"adunit": "unit1"}}}]}, | ||
"httpCalls": [{"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit1", | ||
"body": {"id": "req1-unit1", | ||
"imp": [{"id": "imp1", | ||
"banner": {"w": 100, "h": 200}, | ||
"ext": {"bidder": {"adunit": "unit1"}}}]}}, | ||
"mockResponse": {"status": 400, | ||
"body": ""}}], | ||
"expectedBidResponses": [], | ||
"expectedMakeBidsErrors": [{"value": "bad request", | ||
"comparison": "literal"}]} |
38 changes: 38 additions & 0 deletions
38
adapters/adoppler/adopplertest/supplemental/duplicate-imp.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
{"mockBidRequest": {"id": "req1", | ||
"imp":[{"id": "imp1", | ||
"banner": {"w": 100, | ||
"h": 200}, | ||
"ext": {"bidder": {"adunit": "unit1"}}}, | ||
{"id": "imp1", | ||
"banner": {"w": 100, | ||
"h": 200}, | ||
"ext": {"bidder": {"adunit": "unit2"}}}]}, | ||
"httpCalls": [{"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit1", | ||
"body": {"id": "req1-unit1", | ||
"imp": [{"id": "imp1", | ||
"banner": {"w": 100, "h": 200}, | ||
"ext": {"bidder": {"adunit": "unit1"}}}]}}, | ||
"mockResponse": {"status": 200, | ||
"body": {"id": "req1-imp1-resp1", | ||
"seatbid": [{"bid": [{"id": "req1-imp1-bid1", | ||
"impid": "imp1", | ||
"price": 0.12, | ||
"adm": "<b>a banner</b>"}]}], | ||
"cur": "USD"}}}, | ||
{"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit2", | ||
"body": {"id": "req1-unit2", | ||
"imp": [{"id": "imp1", | ||
"banner": {"w": 100, "h": 200}, | ||
"ext": {"bidder": {"adunit": "unit2"}}}]}}, | ||
"mockResponse": {"status": 200, | ||
"body": {"id": "req1-imp1-resp1", | ||
"seatbid": [{"bid": [{"id": "req1-imp1-bid1", | ||
"impid": "imp1", | ||
"price": 0.12, | ||
"adm": "<b>a banner</b>"}]}], | ||
"cur": "USD"}}}], | ||
"expectedBidResponses": [], | ||
"expectedMakeBidsErrors": [{"value": "duplicate $.imp.id imp1", | ||
"comparison": "literal"}, | ||
{"value": "duplicate $.imp.id imp1", | ||
"comparison": "literal"}]} |
20 changes: 20 additions & 0 deletions
20
adapters/adoppler/adopplertest/supplemental/invalid-impid.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
{"mockBidRequest": {"id": "req1", | ||
"imp":[{"id": "imp1", | ||
"banner": {"w": 100, | ||
"h": 200}, | ||
"ext": {"bidder": {"adunit": "unit1"}}}]}, | ||
"httpCalls": [{"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit1", | ||
"body": {"id": "req1-unit1", | ||
"imp": [{"id": "imp1", | ||
"banner": {"w": 100, "h": 200}, | ||
"ext": {"bidder": {"adunit": "unit1"}}}]}}, | ||
"mockResponse": {"status": 200, | ||
"body": {"id": "req1-imp1-resp1", | ||
"seatbid": [{"bid": [{"id": "req1-imp1-bid1", | ||
"impid": "invalid", | ||
"price": 0.12, | ||
"adm": "<b>a banner</b>"}]}], | ||
"cur": "USD"}}}], | ||
"expectedBidResponses": [], | ||
"expectedMakeBidsErrors": [{"value": "unknown impid: invalid", | ||
"comparison": "literal"}]} |
15 changes: 15 additions & 0 deletions
15
adapters/adoppler/adopplertest/supplemental/invalid-response.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{"mockBidRequest": {"id": "req1", | ||
"imp":[{"id": "imp1", | ||
"banner": {"w": 100, | ||
"h": 200}, | ||
"ext": {"bidder": {"adunit": "unit1"}}}]}, | ||
"httpCalls": [{"expectedRequest": {"uri": "http://adoppler.com/processHeaderBid/unit1", | ||
"body": {"id": "req1-unit1", | ||
"imp": [{"id": "imp1", | ||
"banner": {"w": 100, "h": 200}, | ||
"ext": {"bidder": {"adunit": "unit1"}}}]}}, | ||
"mockResponse": {"status": 200, | ||
"body": "invalid-json"}}], | ||
"expectedBidResponses": [], | ||
"expectedMakeBidsErrors": [{"value": "invalid body: json: cannot unmarshal string into Go value of type openrtb.BidResponse", | ||
"comparison": "literal"}]} |
Oops, something went wrong.