Skip to content
This repository was archived by the owner on Dec 22, 2022. It is now read-only.

Commit 690b8b6

Browse files
trchandraprakashChandra Prakash
and
Chandra Prakash
authored
LunaMedia Adapter (prebid#1285)
Co-authored-by: Chandra Prakash <[email protected]>
1 parent fa56225 commit 690b8b6

25 files changed

+1016
-0
lines changed

adapters/lunamedia/lunamedia.go

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
package lunamedia
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/http"
7+
"text/template"
8+
9+
"github.com/mxmCherry/openrtb"
10+
"github.com/prebid/prebid-server/adapters"
11+
"github.com/prebid/prebid-server/errortypes"
12+
"github.com/prebid/prebid-server/macros"
13+
"github.com/prebid/prebid-server/openrtb_ext"
14+
)
15+
16+
type LunaMediaAdapter struct {
17+
EndpointTemplate template.Template
18+
}
19+
20+
//MakeRequests prepares request information for prebid-server core
21+
func (adapter *LunaMediaAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
22+
errs := make([]error, 0, len(request.Imp))
23+
if len(request.Imp) == 0 {
24+
errs = append(errs, &errortypes.BadInput{Message: "No impression in the bid request"})
25+
return nil, errs
26+
}
27+
pub2impressions, imps, err := getImpressionsInfo(request.Imp)
28+
if len(imps) == 0 {
29+
return nil, err
30+
}
31+
errs = append(errs, err...)
32+
33+
if len(pub2impressions) == 0 {
34+
return nil, errs
35+
}
36+
37+
result := make([]*adapters.RequestData, 0, len(pub2impressions))
38+
for k, imps := range pub2impressions {
39+
bidRequest, err := adapter.buildAdapterRequest(request, &k, imps)
40+
if err != nil {
41+
errs = append(errs, err)
42+
return nil, errs
43+
} else {
44+
result = append(result, bidRequest)
45+
}
46+
}
47+
return result, errs
48+
}
49+
50+
// getImpressionsInfo checks each impression for validity and returns impressions copy with corresponding exts
51+
func getImpressionsInfo(imps []openrtb.Imp) (map[openrtb_ext.ExtImpLunaMedia][]openrtb.Imp, []openrtb.Imp, []error) {
52+
errors := make([]error, 0, len(imps))
53+
resImps := make([]openrtb.Imp, 0, len(imps))
54+
res := make(map[openrtb_ext.ExtImpLunaMedia][]openrtb.Imp)
55+
56+
for _, imp := range imps {
57+
impExt, err := getImpressionExt(&imp)
58+
if err != nil {
59+
errors = append(errors, err)
60+
continue
61+
}
62+
if err := validateImpression(impExt); err != nil {
63+
errors = append(errors, err)
64+
continue
65+
}
66+
//dispatchImpressions
67+
//Group impressions by LunaMedia-specific parameters `pubid
68+
if err := compatImpression(&imp); err != nil {
69+
errors = append(errors, err)
70+
continue
71+
}
72+
if res[*impExt] == nil {
73+
res[*impExt] = make([]openrtb.Imp, 0)
74+
}
75+
res[*impExt] = append(res[*impExt], imp)
76+
resImps = append(resImps, imp)
77+
}
78+
return res, resImps, errors
79+
}
80+
81+
func validateImpression(impExt *openrtb_ext.ExtImpLunaMedia) error {
82+
if impExt.PublisherID == "" {
83+
return &errortypes.BadInput{Message: "No pubid value provided"}
84+
}
85+
return nil
86+
}
87+
88+
//Alter impression info to comply with LunaMedia platform requirements
89+
func compatImpression(imp *openrtb.Imp) error {
90+
imp.Ext = nil //do not forward ext to LunaMedia platform
91+
if imp.Banner != nil {
92+
return compatBannerImpression(imp)
93+
}
94+
return nil
95+
}
96+
97+
func compatBannerImpression(imp *openrtb.Imp) error {
98+
// Create a copy of the banner, since imp is a shallow copy of the original.
99+
100+
bannerCopy := *imp.Banner
101+
banner := &bannerCopy
102+
//As banner.w/h are required fields for LunaMedia platform - take the first format entry
103+
if banner.W == nil || banner.H == nil {
104+
if len(banner.Format) == 0 {
105+
return &errortypes.BadInput{Message: "Expected at least one banner.format entry or explicit w/h"}
106+
}
107+
format := banner.Format[0]
108+
banner.Format = banner.Format[1:]
109+
banner.W = &format.W
110+
banner.H = &format.H
111+
imp.Banner = banner
112+
}
113+
return nil
114+
}
115+
116+
func getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpLunaMedia, error) {
117+
var bidderExt adapters.ExtImpBidder
118+
if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
119+
return nil, &errortypes.BadInput{
120+
Message: err.Error(),
121+
}
122+
}
123+
var LunaMediaExt openrtb_ext.ExtImpLunaMedia
124+
if err := json.Unmarshal(bidderExt.Bidder, &LunaMediaExt); err != nil {
125+
return nil, &errortypes.BadInput{
126+
Message: err.Error(),
127+
}
128+
}
129+
return &LunaMediaExt, nil
130+
}
131+
132+
func (adapter *LunaMediaAdapter) buildAdapterRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpLunaMedia, imps []openrtb.Imp) (*adapters.RequestData, error) {
133+
newBidRequest := createBidRequest(prebidBidRequest, params, imps)
134+
reqJSON, err := json.Marshal(newBidRequest)
135+
if err != nil {
136+
return nil, err
137+
}
138+
139+
headers := http.Header{}
140+
headers.Add("Content-Type", "application/json;charset=utf-8")
141+
headers.Add("Accept", "application/json")
142+
headers.Add("x-openrtb-version", "2.5")
143+
144+
url, err := adapter.buildEndpointURL(params)
145+
if err != nil {
146+
return nil, err
147+
}
148+
149+
return &adapters.RequestData{
150+
Method: "POST",
151+
Uri: url,
152+
Body: reqJSON,
153+
Headers: headers}, nil
154+
}
155+
156+
func createBidRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpLunaMedia, imps []openrtb.Imp) *openrtb.BidRequest {
157+
bidRequest := *prebidBidRequest
158+
bidRequest.Imp = imps
159+
for idx := range bidRequest.Imp {
160+
imp := &bidRequest.Imp[idx]
161+
imp.TagID = params.Placement
162+
}
163+
if bidRequest.Site != nil {
164+
// Need to copy Site as Request is a shallow copy
165+
siteCopy := *bidRequest.Site
166+
bidRequest.Site = &siteCopy
167+
bidRequest.Site.Publisher = nil
168+
bidRequest.Site.Domain = ""
169+
}
170+
if bidRequest.App != nil {
171+
// Need to copy App as Request is a shallow copy
172+
appCopy := *bidRequest.App
173+
bidRequest.App = &appCopy
174+
bidRequest.App.Publisher = nil
175+
}
176+
return &bidRequest
177+
}
178+
179+
// Builds enpoint url based on adapter-specific pub settings from imp.ext
180+
func (adapter *LunaMediaAdapter) buildEndpointURL(params *openrtb_ext.ExtImpLunaMedia) (string, error) {
181+
endpointParams := macros.EndpointTemplateParams{PublisherID: params.PublisherID}
182+
return macros.ResolveMacros(adapter.EndpointTemplate, endpointParams)
183+
}
184+
185+
//MakeBids translates LunaMedia bid response to prebid-server specific format
186+
func (adapter *LunaMediaAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
187+
var msg = ""
188+
if response.StatusCode == http.StatusNoContent {
189+
return nil, nil
190+
}
191+
if response.StatusCode != http.StatusOK {
192+
msg = fmt.Sprintf("Unexpected http status code: %d", response.StatusCode)
193+
return nil, []error{&errortypes.BadServerResponse{Message: msg}}
194+
195+
}
196+
var bidResp openrtb.BidResponse
197+
if err := json.Unmarshal(response.Body, &bidResp); err != nil {
198+
msg = fmt.Sprintf("Bad server response: %d", err)
199+
return nil, []error{&errortypes.BadServerResponse{Message: msg}}
200+
}
201+
if len(bidResp.SeatBid) != 1 {
202+
var msg = fmt.Sprintf("Invalid SeatBids count: %d", len(bidResp.SeatBid))
203+
return nil, []error{&errortypes.BadServerResponse{Message: msg}}
204+
}
205+
206+
seatBid := bidResp.SeatBid[0]
207+
bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid))
208+
209+
for i := 0; i < len(seatBid.Bid); i++ {
210+
bid := seatBid.Bid[i]
211+
bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
212+
Bid: &bid,
213+
BidType: getMediaTypeForImpID(bid.ImpID, internalRequest.Imp),
214+
})
215+
}
216+
return bidResponse, nil
217+
}
218+
219+
// getMediaTypeForImp figures out which media type this bid is for
220+
func getMediaTypeForImpID(impID string, imps []openrtb.Imp) openrtb_ext.BidType {
221+
for _, imp := range imps {
222+
if imp.ID == impID && imp.Video != nil {
223+
return openrtb_ext.BidTypeVideo
224+
}
225+
}
226+
return openrtb_ext.BidTypeBanner
227+
}
228+
229+
// NewLunaMediaAdapter to be called in prebid-server core to create LunaMedia adapter instance
230+
func NewLunaMediaBidder(endpointTemplate string) adapters.Bidder {
231+
template, err := template.New("endpointTemplate").Parse(endpointTemplate)
232+
if err != nil {
233+
return nil
234+
}
235+
return &LunaMediaAdapter{EndpointTemplate: *template}
236+
}

adapters/lunamedia/lunamedia_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package lunamedia
2+
3+
import (
4+
"github.com/prebid/prebid-server/adapters/adapterstest"
5+
"testing"
6+
)
7+
8+
func TestJsonSamples(t *testing.T) {
9+
adapterstest.RunJSONBidderTest(t, "lunamediatest", NewLunaMediaBidder("http://api.lunamedia.io/xp/get?pubid={{.PublisherID}}"))
10+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
{
2+
"mockBidRequest": {
3+
"id": "testid",
4+
"imp": [
5+
{
6+
"id": "testimpid",
7+
"banner": {
8+
"format": [
9+
{
10+
"w": 320,
11+
"h": 250
12+
},
13+
{
14+
"w": 320,
15+
"h": 300
16+
}
17+
],
18+
"w": 320,
19+
"h": 250
20+
},
21+
"ext": {
22+
"bidder": {
23+
"pubid": "19f1b372c7548ec1fe734d2c9f8dc688",
24+
"placement": "dummyplacement"
25+
}
26+
}
27+
}
28+
]
29+
},
30+
31+
"httpCalls": [
32+
{
33+
"expectedRequest": {
34+
"uri": "http://api.lunamedia.io/xp/get?pubid=19f1b372c7548ec1fe734d2c9f8dc688",
35+
"body":{
36+
"id": "testid",
37+
"imp": [{
38+
"id": "testimpid",
39+
"tagid": "dummyplacement",
40+
"banner": {
41+
"format": [{
42+
"w": 320,
43+
"h": 250
44+
}, {
45+
"w": 320,
46+
"h": 300
47+
}],
48+
"w": 320,
49+
"h": 250
50+
}
51+
52+
}]
53+
}
54+
},
55+
"mockResponse": {
56+
"status": 200,
57+
"body": {
58+
"seatbid": [
59+
{
60+
"bid": [
61+
{
62+
"crid": "24080",
63+
"adid": "2068416",
64+
"price": 0.01,
65+
"id": "testid",
66+
"impid": "testimpid",
67+
"cid": "8048"
68+
}
69+
]
70+
}
71+
]
72+
}
73+
}
74+
}
75+
],
76+
77+
"expectedBidResponses": [
78+
{
79+
"currency": "USD",
80+
"bids": [
81+
{
82+
"bid": {
83+
"crid": "24080",
84+
"adid": "2068416",
85+
"price": 0.01,
86+
"id": "testid",
87+
"impid": "testimpid",
88+
"cid": "8048"
89+
},
90+
"type": "banner"
91+
}
92+
]
93+
}
94+
]
95+
}

0 commit comments

Comments
 (0)