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: TheTradeDesk #3738

Merged
merged 5 commits into from
Aug 6, 2024
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
53 changes: 53 additions & 0 deletions adapters/thetradedesk/params_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package thetradedesk

import (
"encoding/json"
"testing"

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

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

// TestInvalidParams makes sure that the TheTradeDesk schema rejects all the imp.ext fields we don't support.
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.BidderTheTradeDesk, json.RawMessage(invalidParam)); err == nil {
t.Errorf("Schema allowed unexpected params: %s", invalidParam)
}
}
}

var validParams = []string{
`{"publisherId": "123456"}`,
`{"publisherId": "pub-123456"}`,
`{"publisherId": "publisherIDAllString"}`,
}

var invalidParams = []string{
``,
`null`,
`true`,
`5`,
`4.2`,
`[]`,
`{}`,
`{"publisherId": 123456}`,
`{"publisherId": 0}`,
}
196 changes: 196 additions & 0 deletions adapters/thetradedesk/thetradedesk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package thetradedesk

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

"github.com/prebid/prebid-server/v2/adapters"
"github.com/prebid/prebid-server/v2/config"
"github.com/prebid/prebid-server/v2/macros"
"github.com/prebid/prebid-server/v2/openrtb_ext"

"github.com/prebid/openrtb/v20/openrtb2"
)

const PREBID_INTEGRATION_TYPE = "1"

type adapter struct {
bidderEndpoint string
}

type ExtImpBidderTheTradeDesk struct {
adapters.ExtImpBidder
}

func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
pubID, err := getPublisherId(request.Imp)

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

modifiedImps := make([]openrtb2.Imp, 0, len(request.Imp))

for _, imp := range request.Imp {

if imp.Banner != nil {
if len(imp.Banner.Format) > 0 {
firstFormat := imp.Banner.Format[0]
bannerCopy := *imp.Banner
bannerCopy.H = &firstFormat.H
bannerCopy.W = &firstFormat.W
imp.Banner = &bannerCopy

}
}

modifiedImps = append(modifiedImps, imp)
}

request.Imp = modifiedImps

if request.Site != nil {
siteCopy := *request.Site
if siteCopy.Publisher != nil {
publisherCopy := *siteCopy.Publisher
if pubID != "" {
publisherCopy.ID = pubID
}
siteCopy.Publisher = &publisherCopy
} else {
siteCopy.Publisher = &openrtb2.Publisher{ID: pubID}
}
request.Site = &siteCopy
} else if request.App != nil {
appCopy := *request.App
if appCopy.Publisher != nil {
publisherCopy := *appCopy.Publisher
if pubID != "" {
publisherCopy.ID = pubID
}
appCopy.Publisher = &publisherCopy
} else {
appCopy.Publisher = &openrtb2.Publisher{ID: pubID}
}
request.App = &appCopy
}

errs := make([]error, 0, len(request.Imp))
reqJSON, err := json.Marshal(request)
andre-gielow-ttd marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
errs = append(errs, err)
return nil, errs
}

headers := http.Header{}
headers.Add("Content-Type", "application/json;charset=utf-8")
headers.Add("Accept", "application/json")
headers.Add("x-integration-type", PREBID_INTEGRATION_TYPE)
return []*adapters.RequestData{{
Method: "POST",
Uri: a.bidderEndpoint,
Body: reqJSON,
Headers: headers,
ImpIDs: openrtb_ext.GetImpIDs(request.Imp),
}}, errs
}

func getPublisherId(impressions []openrtb2.Imp) (string, error) {
for _, imp := range impressions {

var bidderExt ExtImpBidderTheTradeDesk
if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
return "", err
}

var ttdExt openrtb_ext.ExtImpTheTradeDesk
if err := json.Unmarshal(bidderExt.Bidder, &ttdExt); err != nil {
return "", err
}

if ttdExt.PublisherId != "" {
return ttdExt.PublisherId, nil
}
}
return "", nil
andre-gielow-ttd marked this conversation as resolved.
Show resolved Hide resolved
andre-gielow-ttd marked this conversation as resolved.
Show resolved Hide resolved
}

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

if err := adapters.CheckResponseStatusCodeForErrors(response); err != nil {
return nil, []error{err}
}

var bidResponse openrtb2.BidResponse
if err := json.Unmarshal(response.Body, &bidResponse); err != nil {
return nil, []error{err}
andre-gielow-ttd marked this conversation as resolved.
Show resolved Hide resolved
}

bidderResponse := adapters.NewBidderResponse()
bidderResponse.Currency = bidResponse.Cur

for _, seatBid := range bidResponse.SeatBid {
for _, bid := range seatBid.Bid {
bid := bid
andre-gielow-ttd marked this conversation as resolved.
Show resolved Hide resolved

bidType, err := getBidType(bid.MType)

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

b := &adapters.TypedBid{
Bid: &bid,
BidType: bidType,
}
bidderResponse.Bids = append(bidderResponse.Bids, b)
}
}

return bidderResponse, nil
}

func getBidType(markupType openrtb2.MarkupType) (openrtb_ext.BidType, error) {
switch markupType {
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
gargcreation1992 marked this conversation as resolved.
Show resolved Hide resolved
andre-gielow-ttd marked this conversation as resolved.
Show resolved Hide resolved
andre-gielow-ttd marked this conversation as resolved.
Show resolved Hide resolved
andre-gielow-ttd marked this conversation as resolved.
Show resolved Hide resolved
andre-gielow-ttd marked this conversation as resolved.
Show resolved Hide resolved
andre-gielow-ttd marked this conversation as resolved.
Show resolved Hide resolved
andre-gielow-ttd marked this conversation as resolved.
Show resolved Hide resolved
andre-gielow-ttd marked this conversation as resolved.
Show resolved Hide resolved
andre-gielow-ttd marked this conversation as resolved.
Show resolved Hide resolved
andre-gielow-ttd marked this conversation as resolved.
Show resolved Hide resolved
andre-gielow-ttd marked this conversation as resolved.
Show resolved Hide resolved
andre-gielow-ttd marked this conversation as resolved.
Show resolved Hide resolved
andre-gielow-ttd marked this conversation as resolved.
Show resolved Hide resolved
andre-gielow-ttd marked this conversation as resolved.
Show resolved Hide resolved
andre-gielow-ttd marked this conversation as resolved.
Show resolved Hide resolved
andre-gielow-ttd marked this conversation as resolved.
Show resolved Hide resolved
andre-gielow-ttd marked this conversation as resolved.
Show resolved Hide resolved
andre-gielow-ttd marked this conversation as resolved.
Show resolved Hide resolved
andre-gielow-ttd marked this conversation as resolved.
Show resolved Hide resolved
andre-gielow-ttd marked this conversation as resolved.
Show resolved Hide resolved
andre-gielow-ttd marked this conversation as resolved.
Show resolved Hide resolved
andre-gielow-ttd marked this conversation as resolved.
Show resolved Hide resolved
andre-gielow-ttd marked this conversation as resolved.
Show resolved Hide resolved
andre-gielow-ttd marked this conversation as resolved.
Show resolved Hide resolved
andre-gielow-ttd marked this conversation as resolved.
Show resolved Hide resolved
case openrtb2.MarkupBanner:
return openrtb_ext.BidTypeBanner, nil
case openrtb2.MarkupVideo:
return openrtb_ext.BidTypeVideo, nil
case openrtb2.MarkupNative:
return openrtb_ext.BidTypeNative, nil
default:
return "", fmt.Errorf("unsupported mtype: %d", markupType)
}
}

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

if len(config.ExtraAdapterInfo) > 0 {
isValidEndpoint, err := regexp.Match("([a-z]+)$", []byte(config.ExtraAdapterInfo))
if !isValidEndpoint || err != nil {
return nil, errors.New("ExtraAdapterInfo must be a simple string provided by TheTradeDesk")
}
}

urlParams := macros.EndpointTemplateParams{SupplyId: config.ExtraAdapterInfo}
bidderEndpoint, err := macros.ResolveMacros(template, urlParams)

if err != nil {
return nil, fmt.Errorf("unable to resolve endpoint macros: %v", err)
}

return &adapter{
bidderEndpoint: bidderEndpoint,
}, nil
}
Loading
Loading