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: SmartHub #1932

Merged
merged 8 commits into from
Aug 5, 2021
Merged
Show file tree
Hide file tree
Changes from 3 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
54 changes: 54 additions & 0 deletions adapters/smarthub/params_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package smarthub

import (
"encoding/json"
"testing"

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

var validParams = []string{
`{"host":"smarthub.test", "seat":"9Q20EdGxzgWdfPYShScl", "token":"eKmw6alpP3zWQhRCe3flOpz0wpuwRFjW"}`,
}

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.BidderSmartHub, json.RawMessage(validParam)); err != nil {
t.Errorf("Schema rejected smarthub params: %s", validParam)
}
}
}

var invalidParams = []string{
``,
`null`,
`true`,
`5`,
`[]`,
`{}`,
`{"anyparam": "anyvalue"}`,
`{"host":"smarthub.test"}`,
`{"seat":"9Q20EdGxzgWdfPYShScl"}`,
`{"token":"Y9Evrh40ejsrCR4EtidUt1cSxhJsz8X1"}`,
`{"seat":"9Q20EdGxzgWdfPYShScl", "token":"alNYtemWggraDVbhJrsOs9pXc3Eld32E"}`,
`{"host":"smarthub.test", "token":"LNywdP2ebX5iETF8gvBeEoB6Cam64eeq"}`,
`{"host":"smarthub.test", "seat":"9Q20EdGxzgWdfPYShScl"}`,
}

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.BidderSmartHub, json.RawMessage(invalidParam)); err == nil {
t.Errorf("Schema allowed unexpected params: %s", invalidParam)
}
}
}
187 changes: 187 additions & 0 deletions adapters/smarthub/smarthub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package smarthub

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

const (
ADAPTER_VER = "1.0.0"
)

type adapter struct {
endpoint template.Template
}

type bidExt struct {
MediaType string `json:"mediaType"`
}

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) getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtSmartHub, 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 smarthubExt openrtb_ext.ExtSmartHub
if err := json.Unmarshal(bidderExt.Bidder, &smarthubExt); err != nil {
return nil, &errortypes.BadInput{
Message: "Error while unmarshaling bidder extension",
}
}

return &smarthubExt, nil
}

func (a *adapter) buildEndpointURL(params *openrtb_ext.ExtSmartHub) (string, error) {
endpointParams := macros.EndpointTemplateParams{
Host: params.Host,
AccountID: params.Seat,
SourceId: params.Token,
Copy link
Contributor

Choose a reason for hiding this comment

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

I see you pass all three parameters to your URL. Do you need all three to NOT be empty? As of right now, you marked them as required inside static/bidder-params/smarthub.json but they can still be zero-lenght strings. In case you need that any or all three of your parameters to not be empty you could add the minLength property like:

   {
     "$schema": "http://json-schema.org/draft-04/schema#",
     "title": "SmartHub Adapter Params",
     "description": "A schema which validates params accepted by the SmartHub adapter",
     "type": "object",
     "properties": {
       "host": {
         "type": "string",
         "description": "Network host to send request"
 +       "minLength": 1
       },
       "seat": {
         "type": "string",
         "description": "Seat"
 +       "minLength": 1
       },
       "token": {
         "type": "string",
         "description": "Token"
 +       "minLength": 1
       }
     },
     "required": [
       "host",
       "seat",
       "token"
     ]
   }
static/bidder-params/smarthub.json

Without the additions shown above, an all empty parameter req.Imp[i].Ext would be allowed. In other words, for the moment, a test case like this is currently passing:

   var invalidParams = []string{
   	``,
   	`null`,
   	`true`,
   	`5`,
   	`[]`,
   	`{}`,
   	`{"anyparam": "anyvalue"}`,
   	`{"host":"smarthub.test"}`,
   	`{"seat":"9Q20EdGxzgWdfPYShScl"}`,
   	`{"token":"Y9Evrh40ejsrCR4EtidUt1cSxhJsz8X1"}`,
   	`{"seat":"9Q20EdGxzgWdfPYShScl", "token":"alNYtemWggraDVbhJrsOs9pXc3Eld32E"}`,
   	`{"host":"smarthub.test", "token":"LNywdP2ebX5iETF8gvBeEoB6Cam64eeq"}`,
   	`{"host":"smarthub.test", "seat":"9Q20EdGxzgWdfPYShScl"}`,
 + 	`{"host":"", "seat":"", "token":""}`,  // <-- all empty fields test case is passing right now
   }
adapters/smarthub/params_test.go

Copy link
Contributor

Choose a reason for hiding this comment

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

If that's what you intended, it's fine

Copy link
Contributor Author

@SmartHubSolutions SmartHubSolutions Jul 28, 2021

Choose a reason for hiding this comment

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

Yes, thanks a lot for pointing the mistake! All three parameters mustn't be an empty string. I've fixed this in static/bidder-params/smarthub.json and also added possible cases to test into invalidParams

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

func (a *adapter) MakeRequests(
openRTBRequest *openrtb2.BidRequest,
reqInfo *adapters.ExtraRequestInfo,
) (
requestsToBidder []*adapters.RequestData,
errs []error,
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the reason behind naming output parameters? In MakeBids function as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is no reason. I've removed in both

) {
if len(openRTBRequest.Imp) == 0 {
return nil, []error{&errortypes.BadInput{Message: "Missing Imp object"}}
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick: Prebid server core filters out requests with empty []Imp arrays so there's no need for this check. I realize this isn't a lot overhead so you could leave it or remove it if you want.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed this

}

var smarthubExt *openrtb_ext.ExtSmartHub
smarthubExt, err := a.getImpressionExt(&(openRTBRequest.Imp[0]))
if err != nil {
return nil, []error{err}
}

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

reqJSON, 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")
headers.Add("Prebid-Adapter-Ver", ADAPTER_VER)

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

func (a *adapter) MakeBids(
openRTBRequest *openrtb2.BidRequest,
requestToBidder *adapters.RequestData,
bidderRawResponse *adapters.ResponseData,
) (
bidderResponse *adapters.BidderResponse,
errs []error,
) {
if bidderRawResponse.StatusCode == http.StatusNoContent {
return nil, nil
}

if bidderRawResponse.StatusCode == http.StatusBadRequest {
return nil, []error{&errortypes.BadInput{
Message: fmt.Sprintf("Bad Request. %s", string(bidderRawResponse.Body)),
}}
}

if bidderRawResponse.StatusCode == http.StatusServiceUnavailable {
return nil, []error{&errortypes.BadInput{
Message: "Bidder unavailable. Please contact the bidder support.",
}}
}

if bidderRawResponse.StatusCode != http.StatusOK {
return nil, []error{&errortypes.BadServerResponse{
Message: fmt.Sprintf("Status Code: [ %d ] %s", bidderRawResponse.StatusCode, string(bidderRawResponse.Body)),
}}
}

responseBody := bidderRawResponse.Body
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",
}}
}

bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)

bids := bidResp.SeatBid[0].Bid

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

bid := bids[0]
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it always only one bid in response?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, we don't support more than one bid in response


var bidExt bidExt
var bidType openrtb_ext.BidType

if err := json.Unmarshal(bid.Ext, &bidExt); err != nil {
return nil, []error{&errortypes.BadServerResponse{
Message: "Field BidExt is required",
}}
}

bidType, err := getBidType(bidExt)

if err != nil {
return nil, []error{err}
}

bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
Bid: &bid,
BidType: bidType,
})
return bidResponse, nil
}

func getBidType(ext bidExt) (openrtb_ext.BidType, error) {
return openrtb_ext.ParseBidType(ext.MediaType)
}
18 changes: 18 additions & 0 deletions adapters/smarthub/smarthub_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package smarthub

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://{{.Host}}/?seat={{.AccountID}}&token={{.SourceId}}"})

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