Skip to content
This repository was archived by the owner on Nov 20, 2025. It is now read-only.
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
273 changes: 273 additions & 0 deletions adapters/clydo/clydo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
package clydo

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

"github.com/prebid/openrtb/v20/openrtb2"
"github.com/prebid/prebid-server/v3/adapters"
"github.com/prebid/prebid-server/v3/config"
"github.com/prebid/prebid-server/v3/errortypes"
"github.com/prebid/prebid-server/v3/macros"
"github.com/prebid/prebid-server/v3/openrtb_ext"
"github.com/prebid/prebid-server/v3/util/jsonutil"
)

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("Failed to parse endpoint url: %v", err)
}
bidder := &adapter{
endpoint: template,
}
return bidder, nil
}

func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
var requests []*adapters.RequestData
var errors []error

impIds, err := prepareImpIds(request)
if err != nil {
return nil, append(errors, err)
}

headers, err := prepareHeaders(request)
if err != nil {
return nil, append(errors, err)
}

for _, imp := range request.Imp {
reqData, err := a.prepareRequest(request, imp, impIds, headers)
if err != nil {
errors = append(errors, err)
continue
}
requests = append(requests, reqData)
}
return requests, errors
}

func (a *adapter) MakeBids(
request *openrtb2.BidRequest,
requestData *adapters.RequestData,
responseData *adapters.ResponseData,
) (*adapters.BidderResponse, []error) {
if adapters.IsResponseStatusCodeNoContent(responseData) {
return nil, nil
}

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

bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
if response.Cur != "" {
bidResponse.Currency = response.Cur
}

bidTypeMap, err := buildBidTypeMap(request.Imp)
if err != nil {
return nil, []error{err}
}
bids := prepareSeatBids(response.SeatBid, bidTypeMap)
bidResponse.Bids = bids

return bidResponse, nil
}

func (a *adapter) prepareRequest(request *openrtb2.BidRequest, imp openrtb2.Imp, impIds []string, headers http.Header) (*adapters.RequestData, error) {
params, err := prepareExtParams(imp)
if err != nil {
return nil, err
}
endpoint, err := a.prepareEndpoint(params)
if err != nil {
return nil, err
}
body, err := prepareBody(request, imp)
if err != nil {
return nil, err
}

return &adapters.RequestData{
Method: "POST",
Uri: endpoint,
Body: body,
Headers: headers,
ImpIDs: impIds,
}, nil
}

func prepareExtParams(imp openrtb2.Imp) (*openrtb_ext.ImpExtClydo, error) {
var clydoImpExt openrtb_ext.ImpExtClydo
var bidderExt adapters.ExtImpBidder
if len(imp.Ext) == 0 {
return nil, &errortypes.BadInput{
Message: "missing ext",
}
}
if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil {
return nil, &errortypes.BadInput{
Message: "invalid ext",
}
}
if len(bidderExt.Bidder) == 0 {
return nil, &errortypes.BadInput{
Message: "missing ext.bidder",
}
}
if err := jsonutil.Unmarshal(bidderExt.Bidder, &clydoImpExt); err != nil {
return nil, &errortypes.BadInput{
Message: "invalid ext.bidder",
}
}
return &clydoImpExt, nil
}

func (a *adapter) prepareEndpoint(params *openrtb_ext.ImpExtClydo) (string, error) {
partnerId := params.PartnerId
if partnerId == "" {
return "", &errortypes.BadInput{
Message: "invalid partnerId",
}
}

region := params.Region
if region == "" {
region = "us"
}

endpointParams := macros.EndpointTemplateParams{
PartnerId: partnerId,
Region: region,
}
return macros.ResolveMacros(a.endpoint, endpointParams)
}

func prepareBody(request *openrtb2.BidRequest, imp openrtb2.Imp) ([]byte, error) {
reqCopy := *request
reqCopy.Imp = []openrtb2.Imp{imp}

body, err := jsonutil.Marshal(&reqCopy)
if err != nil {
return nil, err
}

return body, nil
}

func prepareHeaders(request *openrtb2.BidRequest) (http.Header, error) {
allHeaders := map[string]string{
"X-OpenRTB-Version": "2.5",
"Accept": "application/json",
"Content-Type": "application/json; charset=utf-8",
}

allHeaders, err := appendDeviceHeaders(allHeaders, request)
if err != nil {
return nil, err
}

headers := make(http.Header)
for k, v := range allHeaders {
headers.Add(k, v)
}

return headers, nil
}

func appendDeviceHeaders(headers map[string]string, request *openrtb2.BidRequest) (map[string]string, error) {
if request.Device == nil {
return nil, &errortypes.BadInput{Message: "Failed to get device headers"}
}

if ipv6 := request.Device.IPv6; ipv6 != "" {
headers["X-Forwarded-For"] = ipv6
}
if ip := request.Device.IP; ip != "" {
headers["X-Forwarded-For"] = ip
}
if ua := request.Device.UA; ua != "" {
headers["User-Agent"] = ua
}

return headers, nil
}

func prepareImpIds(request *openrtb2.BidRequest) ([]string, error) {
impIds := openrtb_ext.GetImpIDs(request.Imp)
if impIds == nil {
return nil, &errortypes.BadInput{Message: "Failed to get imp ids"}
}
return impIds, nil
}

func prepareBidResponse(body []byte) (openrtb2.BidResponse, error) {
var response openrtb2.BidResponse
if err := jsonutil.Unmarshal(body, &response); err != nil {
return response, err
}
return response, nil
}

func prepareSeatBids(seatBids []openrtb2.SeatBid, bidTypeMap map[string]openrtb_ext.BidType) []*adapters.TypedBid {
var typedBids []*adapters.TypedBid

for _, seatBid := range seatBids {
for i := range seatBid.Bid {
bid := &seatBid.Bid[i]
bidType := getMediaTypeForBid(bid, bidTypeMap)
typedBids = append(typedBids, &adapters.TypedBid{
Bid: bid,
BidType: bidType,
})
}
}

return typedBids
}

func buildBidTypeMap(imps []openrtb2.Imp) (map[string]openrtb_ext.BidType, error) {
bidTypeMap := make(map[string]openrtb_ext.BidType, len(imps))
for _, imp := range imps {
if _, exists := bidTypeMap[imp.ID]; exists {
return nil, &errortypes.BadInput{
Message: "Duplicate impression ID found",
}
}

switch {
case imp.Audio != nil:
bidTypeMap[imp.ID] = openrtb_ext.BidTypeAudio
case imp.Video != nil:
bidTypeMap[imp.ID] = openrtb_ext.BidTypeVideo
case imp.Native != nil:
bidTypeMap[imp.ID] = openrtb_ext.BidTypeNative
case imp.Banner != nil:
bidTypeMap[imp.ID] = openrtb_ext.BidTypeBanner
default:
return nil, &errortypes.BadInput{
Message: "Failed to get media type",
}
}
}
return bidTypeMap, nil
}

func getMediaTypeForBid(bid *openrtb2.Bid, bidTypeMap map[string]openrtb_ext.BidType) openrtb_ext.BidType {
if mediaType, ok := bidTypeMap[bid.ImpID]; ok {
return mediaType
}
return openrtb_ext.BidTypeBanner
}
30 changes: 30 additions & 0 deletions adapters/clydo/clydo_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package clydo

import (
"testing"

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

func TestJsonSamples(t *testing.T) {
bidder, buildErr := Builder(openrtb_ext.BidderClydo, config.Adapter{
Endpoint: "http://{{.Region}}.clydo.io/{{.PartnerId}}"},
config.Server{ExternalUrl: "http://hosturl.com"})

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

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

func TestEndpointTemplateMalformed(t *testing.T) {
_, buildErr := Builder(openrtb_ext.BidderClydo, config.Adapter{
Endpoint: "{{Malformed}}"},
config.Server{ExternalUrl: "http://hosturl.com"})

assert.Error(t, buildErr)
}
Loading
Loading