Skip to content
Open
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
238 changes: 238 additions & 0 deletions adapters/floxis/floxis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
package floxis

import (
"fmt"
"net/http"
"net/url"
"regexp"
"strings"
"text/template"

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

type adapter struct {
endpoint *template.Template
}

const (
defaultRegion = "us-e"
defaultPartner = "floxis"
)

// Schema keys are case-sensitive, JSON unmarshalling is not: "REGION" bypasses the schema pattern.
var hostLabel = regexp.MustCompile(`^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$`)
Comment thread
floxis-admin marked this conversation as resolved.

func resolveBidHost(region, partner string) string {
region = strings.ToLower(region)
partner = strings.ToLower(partner)
if region == "" {
region = defaultRegion
}
if partner == "" {
partner = defaultPartner
}
if partner == defaultPartner {
return region
}
return partner + "-" + region
}

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

type impGroup struct {
seat string
host string
imps []openrtb2.Imp
}

func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
var errs []error
var order []string
groups := make(map[string]*impGroup)

for _, imp := range request.Imp {
impExt, err := parseImpExt(imp)
if err != nil {
errs = append(errs, err)
continue
}
host := resolveBidHost(impExt.Region, impExt.Partner)
key := impExt.Seat + "|" + host
group, exists := groups[key]
if !exists {
group = &impGroup{seat: impExt.Seat, host: host}
groups[key] = group
order = append(order, key)
}
group.imps = append(group.imps, imp)
}

if len(order) == 0 {
return nil, errs
}

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

requests := make([]*adapters.RequestData, 0, len(order))
for _, key := range order {
group := groups[key]

endpoint, err := macros.ResolveMacros(a.endpoint, macros.EndpointTemplateParams{Host: group.host})
if err != nil {
errs = append(errs, err)
continue
}

requestCopy := *request
requestCopy.Imp = group.imps

body, err := jsonutil.Marshal(requestCopy)
if err != nil {
errs = append(errs, err)
continue
}

requests = append(requests, &adapters.RequestData{
Method: "POST",
Uri: fmt.Sprintf("%s?seat=%s", endpoint, url.QueryEscape(group.seat)),
Body: body,
Headers: headers,
ImpIDs: openrtb_ext.GetImpIDs(group.imps),
})
}

return requests, errs
}

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

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

var errs []error
bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
if response.Cur != "" {
bidResponse.Currency = response.Cur
}
for _, seatBid := range response.SeatBid {
for i := range seatBid.Bid {
bidType, typeErr := getMediaTypeForBid(request.Imp, seatBid.Bid[i])
if typeErr != nil {
errs = append(errs, typeErr)
continue
}
bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
Bid: &seatBid.Bid[i],
BidType: bidType,
})
}
}
return bidResponse, errs
}

func parseImpExt(imp openrtb2.Imp) (openrtb_ext.ExtImpFloxis, error) {
var bidderExt adapters.ExtImpBidder
if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil {
return openrtb_ext.ExtImpFloxis{}, &errortypes.BadInput{Message: fmt.Sprintf("invalid imp.ext for imp %s: %s", imp.ID, err)}
}
var floxisExt openrtb_ext.ExtImpFloxis
if err := jsonutil.Unmarshal(bidderExt.Bidder, &floxisExt); err != nil {
return openrtb_ext.ExtImpFloxis{}, &errortypes.BadInput{Message: fmt.Sprintf("invalid imp.ext.bidder for imp %s: %s", imp.ID, err)}
}
if floxisExt.Seat == "" {
return openrtb_ext.ExtImpFloxis{}, &errortypes.BadInput{Message: fmt.Sprintf("missing seat for imp %s", imp.ID)}
}
if floxisExt.Region != "" && !hostLabel.MatchString(floxisExt.Region) {
return openrtb_ext.ExtImpFloxis{}, &errortypes.BadInput{Message: fmt.Sprintf("invalid region %q for imp %s", floxisExt.Region, imp.ID)}
}
if floxisExt.Partner != "" && !hostLabel.MatchString(floxisExt.Partner) {
return openrtb_ext.ExtImpFloxis{}, &errortypes.BadInput{Message: fmt.Sprintf("invalid partner %q for imp %s", floxisExt.Partner, imp.ID)}
}
return floxisExt, nil
}

func getMediaTypeForBid(imps []openrtb2.Imp, bid openrtb2.Bid) (openrtb_ext.BidType, error) {
if bid.MType != 0 {
switch bid.MType {
case openrtb2.MarkupBanner:
return openrtb_ext.BidTypeBanner, nil
case openrtb2.MarkupVideo:
return openrtb_ext.BidTypeVideo, nil
case openrtb2.MarkupAudio:
return openrtb_ext.BidTypeAudio, nil
case openrtb2.MarkupNative:
return openrtb_ext.BidTypeNative, nil
default:
return "", &errortypes.BadServerResponse{
Message: fmt.Sprintf("unsupported bid.mtype %d for impression %s", bid.MType, bid.ImpID),
}
}
}
for _, imp := range imps {
if imp.ID != bid.ImpID {
continue
}
formats, resolved := countFormats(imp)
switch {
case formats == 1:
return resolved, nil
case formats > 1:
return "", &errortypes.BadServerResponse{
Message: fmt.Sprintf("bid for multi-format imp %s requires bid.mtype to disambiguate", bid.ImpID),
}
default:
return "", &errortypes.BadServerResponse{
Message: fmt.Sprintf("unable to resolve media type for impression %s", bid.ImpID),
}
}
}
return "", &errortypes.BadServerResponse{
Message: fmt.Sprintf("unable to find impression %s for bid", bid.ImpID),
}
}

func countFormats(imp openrtb2.Imp) (int, openrtb_ext.BidType) {
formats := 0
var resolved openrtb_ext.BidType
if imp.Banner != nil {
formats++
resolved = openrtb_ext.BidTypeBanner
}
if imp.Video != nil {
formats++
resolved = openrtb_ext.BidTypeVideo
}
if imp.Audio != nil {
formats++
resolved = openrtb_ext.BidTypeAudio
}
if imp.Native != nil {
formats++
resolved = openrtb_ext.BidTypeNative
}
return formats, resolved
}
Loading
Loading