Skip to content

LunaMedia Adapter #1285

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
May 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
236 changes: 236 additions & 0 deletions adapters/lunamedia/lunamedia.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
package lunamedia

import (
"encoding/json"
"fmt"
"net/http"
"text/template"

"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"
)

type LunaMediaAdapter struct {
EndpointTemplate template.Template
}

//MakeRequests prepares request information for prebid-server core
func (adapter *LunaMediaAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
errs := make([]error, 0, len(request.Imp))
if len(request.Imp) == 0 {
errs = append(errs, &errortypes.BadInput{Message: "No impression in the bid request"})
return nil, errs
}
pub2impressions, imps, err := getImpressionsInfo(request.Imp)
if len(imps) == 0 {
return nil, err
}
errs = append(errs, err...)

if len(pub2impressions) == 0 {
return nil, errs
}

result := make([]*adapters.RequestData, 0, len(pub2impressions))
for k, imps := range pub2impressions {
bidRequest, err := adapter.buildAdapterRequest(request, &k, imps)
if err != nil {
errs = append(errs, err)
return nil, errs
} else {
result = append(result, bidRequest)
}
}
return result, errs
}

// getImpressionsInfo checks each impression for validity and returns impressions copy with corresponding exts
func getImpressionsInfo(imps []openrtb.Imp) (map[openrtb_ext.ExtImpLunaMedia][]openrtb.Imp, []openrtb.Imp, []error) {
errors := make([]error, 0, len(imps))
resImps := make([]openrtb.Imp, 0, len(imps))
res := make(map[openrtb_ext.ExtImpLunaMedia][]openrtb.Imp)

for _, imp := range imps {
impExt, err := getImpressionExt(&imp)
if err != nil {
errors = append(errors, err)
continue
}
if err := validateImpression(impExt); err != nil {
errors = append(errors, err)
continue
}
//dispatchImpressions
//Group impressions by LunaMedia-specific parameters `pubid
if err := compatImpression(&imp); err != nil {
errors = append(errors, err)
continue
}
if res[*impExt] == nil {
res[*impExt] = make([]openrtb.Imp, 0)
}
res[*impExt] = append(res[*impExt], imp)
resImps = append(resImps, imp)
}
return res, resImps, errors
}

func validateImpression(impExt *openrtb_ext.ExtImpLunaMedia) error {
if impExt.PublisherID == "" {
return &errortypes.BadInput{Message: "No pubid value provided"}
}
return nil
}

//Alter impression info to comply with LunaMedia platform requirements
func compatImpression(imp *openrtb.Imp) error {
imp.Ext = nil //do not forward ext to LunaMedia platform
if imp.Banner != nil {
return compatBannerImpression(imp)
}
return nil
}

func compatBannerImpression(imp *openrtb.Imp) error {
// Create a copy of the banner, since imp is a shallow copy of the original.

bannerCopy := *imp.Banner
banner := &bannerCopy
//As banner.w/h are required fields for LunaMedia platform - take the first format entry
if banner.W == nil || banner.H == nil {
if len(banner.Format) == 0 {
return &errortypes.BadInput{Message: "Expected at least one banner.format entry or explicit w/h"}
}
format := banner.Format[0]
banner.Format = banner.Format[1:]
banner.W = &format.W
banner.H = &format.H
imp.Banner = banner
}
return nil
}

func getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpLunaMedia, error) {
var bidderExt adapters.ExtImpBidder
if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
return nil, &errortypes.BadInput{
Message: err.Error(),
}
}
var LunaMediaExt openrtb_ext.ExtImpLunaMedia
if err := json.Unmarshal(bidderExt.Bidder, &LunaMediaExt); err != nil {
return nil, &errortypes.BadInput{
Message: err.Error(),
}
}
return &LunaMediaExt, nil
}

func (adapter *LunaMediaAdapter) buildAdapterRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpLunaMedia, imps []openrtb.Imp) (*adapters.RequestData, error) {
newBidRequest := createBidRequest(prebidBidRequest, params, imps)
reqJSON, err := json.Marshal(newBidRequest)
if err != nil {
return nil, err
}

headers := http.Header{}
headers.Add("Content-Type", "application/json;charset=utf-8")
headers.Add("Accept", "application/json")
headers.Add("x-openrtb-version", "2.5")

url, err := adapter.buildEndpointURL(params)
if err != nil {
return nil, err
}

return &adapters.RequestData{
Method: "POST",
Uri: url,
Body: reqJSON,
Headers: headers}, nil
}

func createBidRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpLunaMedia, imps []openrtb.Imp) *openrtb.BidRequest {
bidRequest := *prebidBidRequest
bidRequest.Imp = imps
for idx := range bidRequest.Imp {
imp := &bidRequest.Imp[idx]
imp.TagID = params.Placement
}
if bidRequest.Site != nil {
// Need to copy Site as Request is a shallow copy
siteCopy := *bidRequest.Site
bidRequest.Site = &siteCopy
bidRequest.Site.Publisher = nil
bidRequest.Site.Domain = ""
}
if bidRequest.App != nil {
// Need to copy App as Request is a shallow copy
appCopy := *bidRequest.App
bidRequest.App = &appCopy
bidRequest.App.Publisher = nil
}
return &bidRequest
}

// Builds enpoint url based on adapter-specific pub settings from imp.ext
func (adapter *LunaMediaAdapter) buildEndpointURL(params *openrtb_ext.ExtImpLunaMedia) (string, error) {
endpointParams := macros.EndpointTemplateParams{PublisherID: params.PublisherID}
return macros.ResolveMacros(adapter.EndpointTemplate, endpointParams)
}

//MakeBids translates LunaMedia bid response to prebid-server specific format
func (adapter *LunaMediaAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
var msg = ""
if response.StatusCode == http.StatusNoContent {
return nil, nil
}
if response.StatusCode != http.StatusOK {
msg = fmt.Sprintf("Unexpected http status code: %d", response.StatusCode)
return nil, []error{&errortypes.BadServerResponse{Message: msg}}

}
var bidResp openrtb.BidResponse
if err := json.Unmarshal(response.Body, &bidResp); err != nil {
msg = fmt.Sprintf("Bad server response: %d", err)
return nil, []error{&errortypes.BadServerResponse{Message: msg}}
}
if len(bidResp.SeatBid) != 1 {
var msg = fmt.Sprintf("Invalid SeatBids count: %d", len(bidResp.SeatBid))
return nil, []error{&errortypes.BadServerResponse{Message: msg}}
}

seatBid := bidResp.SeatBid[0]
bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid))

for i := 0; i < len(seatBid.Bid); i++ {
bid := seatBid.Bid[i]
bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
Bid: &bid,
BidType: getMediaTypeForImpID(bid.ImpID, internalRequest.Imp),
})
}
return bidResponse, nil
}

// getMediaTypeForImp figures out which media type this bid is for
func getMediaTypeForImpID(impID string, imps []openrtb.Imp) openrtb_ext.BidType {
for _, imp := range imps {
if imp.ID == impID && imp.Video != nil {
return openrtb_ext.BidTypeVideo
}
}
return openrtb_ext.BidTypeBanner
}

// NewLunaMediaAdapter to be called in prebid-server core to create LunaMedia adapter instance
func NewLunaMediaBidder(endpointTemplate string) adapters.Bidder {
template, err := template.New("endpointTemplate").Parse(endpointTemplate)
if err != nil {
return nil
}
return &LunaMediaAdapter{EndpointTemplate: *template}
}
10 changes: 10 additions & 0 deletions adapters/lunamedia/lunamedia_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package lunamedia

import (
"github.com/prebid/prebid-server/adapters/adapterstest"
"testing"
)

func TestJsonSamples(t *testing.T) {
adapterstest.RunJSONBidderTest(t, "lunamediatest", NewLunaMediaBidder("http://api.lunamedia.io/xp/get?pubid={{.PublisherID}}"))
}
95 changes: 95 additions & 0 deletions adapters/lunamedia/lunamediatest/exemplary/banner.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
{
"mockBidRequest": {
"id": "testid",
"imp": [
{
"id": "testimpid",
"banner": {
"format": [
{
"w": 320,
"h": 250
},
{
"w": 320,
"h": 300
}
],
"w": 320,
"h": 250
},
"ext": {
"bidder": {
"pubid": "19f1b372c7548ec1fe734d2c9f8dc688",
"placement": "dummyplacement"
}
}
}
]
},

"httpCalls": [
{
"expectedRequest": {
"uri": "http://api.lunamedia.io/xp/get?pubid=19f1b372c7548ec1fe734d2c9f8dc688",
"body":{
"id": "testid",
"imp": [{
"id": "testimpid",
"tagid": "dummyplacement",
"banner": {
"format": [{
"w": 320,
"h": 250
}, {
"w": 320,
"h": 300
}],
"w": 320,
"h": 250
}

}]
}
},
"mockResponse": {
"status": 200,
"body": {
"seatbid": [
{
"bid": [
{
"crid": "24080",
"adid": "2068416",
"price": 0.01,
"id": "testid",
"impid": "testimpid",
"cid": "8048"
}
]
}
]
}
}
}
],

"expectedBidResponses": [
{
"currency": "USD",
"bids": [
{
"bid": {
"crid": "24080",
"adid": "2068416",
"price": 0.01,
"id": "testid",
"impid": "testimpid",
"cid": "8048"
},
"type": "banner"
}
]
}
]
}
Loading