Skip to content

Commit

Permalink
New Adapter : Kayzen (prebid#1838)
Browse files Browse the repository at this point in the history
* Add Kayzen Adapter
  • Loading branch information
raghuteja authored and sachin-pubmatic committed Aug 2, 2021
1 parent 13cc59b commit fcff331
Show file tree
Hide file tree
Showing 23 changed files with 1,516 additions and 0 deletions.
154 changes: 154 additions & 0 deletions adapters/kayzen/kayzen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package kayzen

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

"github.com/mxmCherry/openrtb/v15/openrtb2"
"github.com/prebid/prebid-server/adapters"
"github.com/prebid/prebid-server/config"
"github.com/prebid/prebid-server/errortypes"
"github.com/prebid/prebid-server/macros"
"github.com/prebid/prebid-server/openrtb_ext"
)

type adapter struct {
endpoint template.Template
}

// Builder builds a new instance of the Kayzen adapter for the given bidder with the given config.
func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
template, err := template.New("endpointTemplate").Parse(config.Endpoint)
if err != nil {
return nil, fmt.Errorf("unable to parse endpoint url template: %v", err)
}

bidder := &adapter{
endpoint: *template,
}
return bidder, nil
}

func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) (requests []*adapters.RequestData, errors []error) {
var kayzenExt *openrtb_ext.ExtKayzen
var err error

if len(request.Imp) > 0 {
kayzenExt, err = a.getImpressionExt(&(request.Imp[0]))
if err != nil {
errors = append(errors, err)
}
request.Imp[0].Ext = nil
} else {
errors = append(errors, &errortypes.BadInput{
Message: "Missing Imp Object",
})
}

if len(errors) > 0 {
return nil, errors
}

url, err := a.buildEndpointURL(kayzenExt)
if err != nil {
return nil, []error{err}
}

requestJSON, err := json.Marshal(request)
if err != nil {
return nil, []error{err}
}

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

return []*adapters.RequestData{{
Method: http.MethodPost,
Body: requestJSON,
Uri: url,
Headers: headers,
}}, nil
}

func (a *adapter) getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtKayzen, error) {
var bidderExt adapters.ExtImpBidder
if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
return nil, &errortypes.BadInput{
Message: "Bidder extension not provided or can't be unmarshalled",
}
}
var kayzenExt openrtb_ext.ExtKayzen
if err := json.Unmarshal(bidderExt.Bidder, &kayzenExt); err != nil {
return nil, &errortypes.BadInput{
Message: "Error while unmarshaling bidder extension",
}
}
return &kayzenExt, nil
}

func (a *adapter) buildEndpointURL(params *openrtb_ext.ExtKayzen) (string, error) {
endpointParams := macros.EndpointTemplateParams{
ZoneID: params.Zone,
AccountID: params.Exchange,
}
return macros.ResolveMacros(a.endpoint, endpointParams)
}

func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
if response.StatusCode == http.StatusNoContent {
return nil, nil
}

if response.StatusCode == http.StatusBadRequest {
err := &errortypes.BadInput{
Message: "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.",
}
return nil, []error{err}
}

if response.StatusCode != http.StatusOK {
err := &errortypes.BadServerResponse{
Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", response.StatusCode),
}
return nil, []error{err}
}

var bidResp openrtb2.BidResponse
if err := json.Unmarshal(response.Body, &bidResp); err != nil {
return nil, []error{&errortypes.BadServerResponse{
Message: "Bad Server Response",
}}
}

bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)

var errs []error
for _, seatBid := range bidResp.SeatBid {
for _, bid := range seatBid.Bid {
b := &adapters.TypedBid{
Bid: &bid,
BidType: getMediaTypeForImp(bid.ImpID, internalRequest.Imp),
}
bidResponse.Bids = append(bidResponse.Bids, b)
}
}
return bidResponse, errs
}

func getMediaTypeForImp(impID string, imps []openrtb2.Imp) openrtb_ext.BidType {
for _, imp := range imps {
if imp.ID == impID {
if imp.Banner != nil {
return openrtb_ext.BidTypeBanner
} else if imp.Video != nil {
return openrtb_ext.BidTypeVideo
} else if imp.Native != nil {
return openrtb_ext.BidTypeNative
}
}
}
return openrtb_ext.BidTypeBanner
}
29 changes: 29 additions & 0 deletions adapters/kayzen/kayzen_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package kayzen

import (
"testing"

"github.com/prebid/prebid-server/adapters/adapterstest"
"github.com/prebid/prebid-server/config"
"github.com/prebid/prebid-server/openrtb_ext"
"github.com/stretchr/testify/assert"
)

func TestJsonSamples(t *testing.T) {
bidder, buildErr := Builder(openrtb_ext.BidderKayzen, config.Adapter{
Endpoint: "https://example-{{.ZoneID}}.com/?exchange={{.AccountID}}",
})

if buildErr != nil {
t.Fatalf("Builder returned unexpected error %v", buildErr)
}

adapterstest.RunJSONBidderTest(t, "kayzentest", bidder)
}

func TestEndpointTemplateMalformed(t *testing.T) {
_, buildErr := Builder(openrtb_ext.BidderKayzen, config.Adapter{
Endpoint: "{{Malformed}}"})

assert.Error(t, buildErr)
}
142 changes: 142 additions & 0 deletions adapters/kayzen/kayzentest/exemplary/banner-app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
{
"mockBidRequest": {
"id": "some-request-id",
"device": {
"ua": "test-user-agent",
"ip": "123.123.123.123",
"language": "en",
"dnt": 0
},
"tmax": 1000,
"user": {
"buyeruid": "0000-000-000-0000"
},
"app": {
"publisher": {
"id": "123456789"
},
"cat": [
"IAB22-1"
],
"bundle": "com.app.awesome",
"name": "Awesome App",
"domain": "awesomeapp.com",
"id": "123456789"
},
"imp": [
{
"id": "some-impression-id",
"banner": {
"w": 320,
"h": 50
},
"ext": {
"bidder": {
"zone": "dc",
"exchange": "ex"
}
}
}
]
},
"httpCalls": [
{
"expectedRequest": {
"headers": {
"Content-Type": [
"application/json;charset=utf-8"
],
"Accept": [
"application/json"
]
},
"uri": "https://example-dc.com/?exchange=ex",
"body": {
"id": "some-request-id",
"device": {
"ua": "test-user-agent",
"ip": "123.123.123.123",
"language": "en",
"dnt": 0
},
"imp": [
{
"id": "some-impression-id",
"banner": {
"w": 320,
"h": 50
}
}
],
"app": {
"id": "123456789",
"name": "Awesome App",
"bundle": "com.app.awesome",
"domain": "awesomeapp.com",
"cat": [
"IAB22-1"
],
"publisher": {
"id": "123456789"
}
},
"user": {
"buyeruid": "0000-000-000-0000"
},
"tmax": 1000
}
},
"mockResponse": {
"status": 200,
"body": {
"id": "awesome-resp-id",
"seatbid": [
{
"bid": [
{
"id": "a3ae1b4e2fc24a4fb45540082e98e161",
"impid": "1",
"price": 3.5,
"adm": "awesome-markup",
"adomain": [
"awesome.com"
],
"crid": "20",
"w": 320,
"h": 50
}
],
"type": "banner",
"seat": "kayzen-prebid"
}
],
"cur": "USD",
"ext": {
}
}
}
}
],
"expectedBidResponses": [
{
"bids": [
{
"bid": {
"id": "a3ae1b4e2fc24a4fb45540082e98e161",
"impid": "1",
"price": 3.5,
"adm": "awesome-markup",
"adomain": [
"awesome.com"
],
"crid": "20",
"w": 320,
"h": 50
},
"type": "banner"
}
]
}
]
}

Loading

0 comments on commit fcff331

Please sign in to comment.