Skip to content
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

New adapter: Xeworks #2752

Merged
merged 9 commits into from May 29, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
53 changes: 53 additions & 0 deletions adapters/xeworks/params_test.go
@@ -0,0 +1,53 @@
package xeworks

import (
"encoding/json"
"testing"

"github.com/prebid/prebid-server/openrtb_ext"
)

var validParams = []string{
`{"env":"xe-stage", "pid":"123456"}`,
}

func TestValidParams(t *testing.T) {
validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
if err != nil {
t.Fatalf("Failed to fetch the json-schemas. %v", err)
}

for _, validParam := range validParams {
if err := validator.Validate(openrtb_ext.BidderXeworks, json.RawMessage(validParam)); err != nil {
t.Errorf("Schema rejected xeworks params: %s", validParam)
}
}
}

var invalidParams = []string{
``,
`null`,
`true`,
`5`,
`[]`,
`{}`,
`{"some": "param"}`,
`{"env":"xe-stage"}`,
`{"pid":"1234"}`,
`{"othervalue":"Lorem ipsum"}`,
`{"env":"xe-stage", pid:""}`,
`{"env":"", pid:"1234"}`,
}

func TestInvalidParams(t *testing.T) {
validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
if err != nil {
t.Fatalf("Failed to fetch the json-schemas. %v", err)
}

for _, invalidParam := range invalidParams {
if err := validator.Validate(openrtb_ext.BidderXeworks, json.RawMessage(invalidParam)); err == nil {
t.Errorf("Schema allowed unexpected params: %s", invalidParam)
}
}
}
180 changes: 180 additions & 0 deletions adapters/xeworks/xeworks.go
@@ -0,0 +1,180 @@
package xeworks

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

"github.com/prebid/openrtb/v19/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"
"github.com/prebid/prebid-server/util/httputil"
)

// { prebid: { type: 'banner' } }
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved

type bidType struct {
Type string `json:"type"`
}

type bidExt struct {
Prebid bidType `json:"prebid"`
}

type adapter struct {
endpoint *template.Template
}

func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (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) buildEndpointFromRequest(imp *openrtb2.Imp) (string, error) {
impExtRaw := imp.Ext
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
var impExt adapters.ExtImpBidder

gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
if err := json.Unmarshal(impExtRaw, &impExt); err != nil {
return "", &errortypes.BadInput{
Message: "Bidder impression extension can't be deserialized",
}
}

var xeworksExt openrtb_ext.ExtXeworks
if err := json.Unmarshal(impExt.Bidder, &xeworksExt); err != nil {
return "", &errortypes.BadInput{
Message: "Xeworks extenson can't be deserialized",
}
}

endpointParams := macros.EndpointTemplateParams{
Host: xeworksExt.Env,
SourceId: xeworksExt.Pid,
}

return macros.ResolveMacros(a.endpoint, endpointParams)
}

func (a *adapter) MakeRequests(openRTBRequest *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
if len(openRTBRequest.Imp) == 0 {
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
return nil, []error{
&errortypes.BadInput{
Message: "Imp array can't be empty",
},
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you are accessing imp[0] in buildEndpointFromRequest method but please be aware that there could be multiple impression in the request. Read more here.
You probably would need a for loop here to handle multiple impressions in the bidRequest.


requests := make([]*adapters.RequestData, 0, len(openRTBRequest.Imp))
errs := make([]error, 0)

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

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

for _, imp := range openRTBRequest.Imp {
endpoint, err := a.buildEndpointFromRequest(&imp)
if err != nil {
errs = append(errs, err)
continue
}

request := &adapters.RequestData{
Method: http.MethodPost,
Body: body,
Uri: endpoint,
Headers: headers,
}

requests = append(requests, request)
}

return requests, errs
}

func (a *adapter) MakeBids(openRTBRequest *openrtb2.BidRequest, requestToBidder *adapters.RequestData, bidderRawResponse *adapters.ResponseData) (*adapters.BidderResponse, []error) {
if httputil.IsResponseStatusCodeNoContent(bidderRawResponse) {
return nil, nil
}

if bidderRawResponse.StatusCode == http.StatusServiceUnavailable {
return nil, []error{&errortypes.BadInput{
Message: "Bidder unavailable. Please contact the bidder support.",
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
}}
}

if err := httputil.CheckResponseStatusCodeForErrors(bidderRawResponse); err != nil {
return nil, []error{err}
}
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved

responseBody := bidderRawResponse.Body
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
var bidResp openrtb2.BidResponse
if err := json.Unmarshal(responseBody, &bidResp); err != nil {
return nil, []error{err}
}

if len(bidResp.SeatBid) == 0 {
return nil, []error{&errortypes.BadServerResponse{
Message: "Array SeatBid cannot be empty",
}}
}

return prepareBidResponse(bidResp.SeatBid)
}

func prepareBidResponse(seats []openrtb2.SeatBid) (*adapters.BidderResponse, []error) {
errs := make([]error, 0)
bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(seats))

for seatId, seatBid := range seats {
if len(seatBid.Bid) == 0 {
errs = append(errs, &errortypes.BadServerResponse{
Message: fmt.Sprintf("Array SeatBid[%d].Bid cannot be empty", seatId),
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
})
}

for bidId, bid := range seatBid.Bid {
var bidExt bidExt
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know you are using seatId in the below error messaging but IMHO this is just be iterator values like 0, 1, 2 which won't be helpful in logging, I think. You might wanna remove it.

if err := json.Unmarshal(bid.Ext, &bidExt); err != nil {
errs = append(errs, &errortypes.BadServerResponse{
Message: fmt.Sprintf("Couldn't parse SeatBid[%d].Bid[%d].Ext, err: %s", seatId, bidId, err.Error()),
})
continue
}

bidType, err := openrtb_ext.ParseBidType(bidExt.Prebid.Type)

gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
errs = append(errs, &errortypes.BadServerResponse{
Message: fmt.Sprintf("SeatBid[%d].Bid[%d].Ext.Prebid.Type expects one of the following values: 'banner', 'native', 'video', 'audio', got '%s'", seatId, bidId, bidExt.Prebid.Type),
})
}

// create copy if bid struct since without it bid address get's polluted with previous value
// because of range
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
bidCopy := bid
bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
Bid: &bidCopy,
BidType: bidType,
})
}
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
}

return bidResponse, errs
}
27 changes: 27 additions & 0 deletions adapters/xeworks/xeworks_test.go
@@ -0,0 +1,27 @@
package xeworks

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.BidderSmartHub,
config.Adapter{
Endpoint: "http://prebid-{{.Host}}.xe.works/?pid={{.SourceId}}",
},
config.Server{
ExternalUrl: "http://hosturl.com",
GvlID: 1,
DataCenter: "2",
},
)

assert.NoError(t, buildErr)
adapterstest.RunJSONBidderTest(t, "xeworkstest", bidder)
}