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

Commit

Permalink
NinthDecimal Adapter (prebid#1249)
Browse files Browse the repository at this point in the history
Co-authored-by: Chandra Prakash <chandra.prakash@advangelists.com>
  • Loading branch information
trchandraprakash and Chandra Prakash authored Apr 24, 2020
1 parent 2fc5dcc commit f993886
Show file tree
Hide file tree
Showing 25 changed files with 1,016 additions and 0 deletions.
236 changes: 236 additions & 0 deletions adapters/ninthdecimal/ninthdecimal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
package ninthdecimal

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 NinthDecimalAdapter struct {
EndpointTemplate template.Template
}

//MakeRequests prepares request information for prebid-server core
func (adapter *NinthDecimalAdapter) 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.ExtImpNinthDecimal][]openrtb.Imp, []openrtb.Imp, []error) {
errors := make([]error, 0, len(imps))
resImps := make([]openrtb.Imp, 0, len(imps))
res := make(map[openrtb_ext.ExtImpNinthDecimal][]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 NinthDecimal-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.ExtImpNinthDecimal) error {
if impExt.PublisherID == "" {
return &errortypes.BadInput{Message: "No pubid value provided"}
}
return nil
}

//Alter impression info to comply with NinthDecimal platform requirements
func compatImpression(imp *openrtb.Imp) error {
imp.Ext = nil //do not forward ext to NinthDecimal 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 NinthDecimal 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.ExtImpNinthDecimal, error) {
var bidderExt adapters.ExtImpBidder
if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
return nil, &errortypes.BadInput{
Message: err.Error(),
}
}
var NinthDecimalExt openrtb_ext.ExtImpNinthDecimal
if err := json.Unmarshal(bidderExt.Bidder, &NinthDecimalExt); err != nil {
return nil, &errortypes.BadInput{
Message: err.Error(),
}
}
return &NinthDecimalExt, nil
}

func (adapter *NinthDecimalAdapter) buildAdapterRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpNinthDecimal, 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.ExtImpNinthDecimal, 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 *NinthDecimalAdapter) buildEndpointURL(params *openrtb_ext.ExtImpNinthDecimal) (string, error) {
endpointParams := macros.EndpointTemplateParams{PublisherID: params.PublisherID}
return macros.ResolveMacros(adapter.EndpointTemplate, endpointParams)
}

//MakeBids translates NinthDecimal bid response to prebid-server specific format
func (adapter *NinthDecimalAdapter) 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
}

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

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

func TestJsonSamples(t *testing.T) {
adapterstest.RunJSONBidderTest(t, "ninthdecimaltest", NewNinthDecimalBidder("http://rtb.ninthdecimal.com/xp/get?pubid={{.PublisherID}}"))
}
95 changes: 95 additions & 0 deletions adapters/ninthdecimal/ninthdecimaltest/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://rtb.ninthdecimal.com/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

0 comments on commit f993886

Please sign in to comment.