Skip to content
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
272 changes: 221 additions & 51 deletions adapters/sparteo/sparteo.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,33 @@ package sparteo
import (
"fmt"
"net/http"
"net/url"
"strings"

"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/openrtb_ext"
"github.com/prebid/prebid-server/v4/util/jsonutil"
"github.com/prebid/prebid-server/v4/util/ptrutil"
)

type adapter struct {
endpoint string
bidderName string
endpoint string
}

type extBidWrapper struct {
Prebid openrtb_ext.ExtBidPrebid `json:"prebid"`
}

const unknownValue = "unknown"

func Builder(bidderName openrtb_ext.BidderName, cfg config.Adapter, server config.Server) (adapters.Bidder, error) {
return &adapter{
endpoint: cfg.Endpoint,
bidderName: string(bidderName),
}, nil
bidder := &adapter{
endpoint: cfg.Endpoint,
}
return bidder, nil
}

func parseExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpSparteo, error) {
Expand All @@ -47,32 +51,61 @@ func parseExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpSparteo, error) {

func (a *adapter) MakeRequests(req *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
request := *req
var errs []error

request.Imp = make([]openrtb2.Imp, len(req.Imp))
copy(request.Imp, req.Imp)

if req.Site != nil {
siteCopy := *req.Site
request.Site = &siteCopy
if req.Site.Publisher != nil {
pubCopy := *req.Site.Publisher
request.Site.Publisher = &pubCopy
}
}

if req.Site != nil && req.Site.Publisher != nil {
publisherCopy := *req.Site.Publisher
request.Site.Publisher = &publisherCopy
if req.App != nil {
appCopy := *req.App
request.App = &appCopy
if req.App.Publisher != nil {
pubCopy := *req.App.Publisher
request.App.Publisher = &pubCopy
}
}

var errs []error
var siteNetworkId string
var siteDomain, appDomain, bundle string
if request.Site != nil {
if sd := resolveSiteDomain(request.Site); sd != nil {
siteDomain = *sd
if siteDomain == unknownValue {
errs = append(errs, &errortypes.BadInput{
Message: "Domain not found. Missing the site.domain or the site.page field",
})
}
}
} else if request.App != nil {
if ad := resolveAppDomain(request.App); ad != nil {
appDomain = *ad
}
if b := resolveBundle(request.App); b != nil {
bundle = *b
if bundle == unknownValue {
errs = append(errs, &errortypes.BadInput{
Message: "Bundle not found. Missing the app.bundle field.",
})
}
}
}

var networkID string
for i, imp := range request.Imp {
extImpSparteo, err := parseExt(&imp)
if err != nil {
errs = append(errs, err)
continue
}

if siteNetworkId == "" && extImpSparteo.NetworkId != "" {
siteNetworkId = extImpSparteo.NetworkId
if networkID == "" && extImpSparteo.NetworkId != "" {
networkID = extImpSparteo.NetworkId
}

var extMap map[string]interface{}
Expand All @@ -93,12 +126,10 @@ func (a *adapter) MakeRequests(req *openrtb2.BidRequest, reqInfo *adapters.Extra
sparteoMap["params"] = paramsMap
}

bidderObj, ok := extMap["bidder"].(map[string]interface{})
if ok {
if bidderObj, ok := extMap["bidder"].(map[string]interface{}); ok {
delete(extMap, "bidder")

for key, value := range bidderObj {
paramsMap[key] = value
for k, v := range bidderObj {
paramsMap[k] = v
}
}

Expand All @@ -107,40 +138,32 @@ func (a *adapter) MakeRequests(req *openrtb2.BidRequest, reqInfo *adapters.Extra
errs = append(errs, fmt.Errorf("ignoring imp id=%s, error while marshaling updated ext, err: %s", imp.ID, err))
continue
}

request.Imp[i].Ext = updatedExt
}

if request.Site != nil && request.Site.Publisher != nil && siteNetworkId != "" {
var pubExt map[string]interface{}
if request.Site.Publisher.Ext != nil {
if err := jsonutil.Unmarshal(request.Site.Publisher.Ext, &pubExt); err != nil {
pubExt = make(map[string]interface{})
}
} else {
pubExt = make(map[string]interface{})
}

var paramsMap map[string]interface{}
if raw, ok := pubExt["params"]; ok {
if paramsMap, ok = raw.(map[string]interface{}); !ok {
paramsMap = make(map[string]interface{})
}
} else {
paramsMap = make(map[string]interface{})
}

paramsMap["networkId"] = siteNetworkId
pubExt["params"] = paramsMap
var pub *openrtb2.Publisher
var pubExt string

if request.Site != nil {
pub = ensurePublisher(request.Site.Publisher)
request.Site.Publisher = pub
pubExt = "site.publisher.ext"
} else if request.App != nil {
pub = ensurePublisher(request.App.Publisher)
request.App.Publisher = pub
pubExt = "app.publisher.ext"
} else {
request.Site = &openrtb2.Site{}
pub = ensurePublisher(request.Site.Publisher)
request.Site.Publisher = pub
pubExt = "site.publisher.ext"
}

updatedPubExt, err := jsonutil.Marshal(pubExt)
if err != nil {
errs = append(errs, &errortypes.BadInput{
Message: fmt.Sprintf("Error marshaling site.publisher.ext: %s", err),
})
} else {
request.Site.Publisher.Ext = jsonutil.RawMessage(updatedPubExt)
}
ext, err := updatePublisherExtension(&pub.Ext, networkID, pubExt)
if err != nil {
errs = append(errs, err)
} else {
pub.Ext = ext
}

body, err := jsonutil.Marshal(request)
Expand All @@ -149,9 +172,14 @@ func (a *adapter) MakeRequests(req *openrtb2.BidRequest, reqInfo *adapters.Extra
return nil, errs
}

uri, err := a.buildEndpointURL(networkID, siteDomain, appDomain, bundle)
if err != nil {
return nil, []error{err}
}

requestData := &adapters.RequestData{
Method: http.MethodPost,
Uri: a.endpoint,
Uri: uri,
Body: body,
ImpIDs: openrtb_ext.GetImpIDs(request.Imp),
Headers: http.Header{
Expand All @@ -162,6 +190,148 @@ func (a *adapter) MakeRequests(req *openrtb2.BidRequest, reqInfo *adapters.Extra
return []*adapters.RequestData{requestData}, errs
}

func normalizeHostname(host string) string {
host = strings.TrimSpace(host)
if host == "" {
return ""
}

u, err := url.Parse(host)
if err != nil || u.Hostname() == "" {
if i := strings.Index(host, ":"); i >= 0 {
host = host[:i]
} else if i := strings.Index(host, "/"); i >= 0 {
host = host[:i]
}
} else {
host = u.Hostname()
}

host = strings.ToLower(host)
host = strings.TrimSuffix(host, ".")
host = strings.TrimPrefix(host, "www.")

if host == "null" {
return ""
}
return host
}

func resolveSiteDomain(site *openrtb2.Site) *string {
if site != nil {
if d := normalizeHostname(site.Domain); d != "" {
return ptrutil.ToPtr(d)
}
if fromPage := normalizeHostname(site.Page); fromPage != "" {
return ptrutil.ToPtr(fromPage)
}
return ptrutil.ToPtr(unknownValue)
}
return nil
}

func resolveAppDomain(app *openrtb2.App) *string {
if app != nil {
if d := normalizeHostname(app.Domain); d != "" {
return ptrutil.ToPtr(d)
}
return ptrutil.ToPtr(unknownValue)
}
return nil
}

func resolveBundle(app *openrtb2.App) *string {
if app == nil {
return nil
}

raw := app.Bundle
if strings.TrimSpace(raw) == "" {
return ptrutil.ToPtr(unknownValue)
}

b := strings.TrimSpace(raw)
if strings.EqualFold(b, "null") {
return ptrutil.ToPtr(unknownValue)
}

return ptrutil.ToPtr(b)
}

func ensurePublisher(p *openrtb2.Publisher) *openrtb2.Publisher {
if p == nil {
p = &openrtb2.Publisher{}
}
if p.Ext == nil {
p.Ext = jsonutil.RawMessage("{}")
}

return p
}

func updatePublisherExtension(targetExt *jsonutil.RawMessage, networkID, fieldPath string) ([]byte, error) {
var pubExt map[string]interface{}
if err := jsonutil.Unmarshal(*targetExt, &pubExt); err != nil {
pubExt = make(map[string]interface{})
}

params, ok := pubExt["params"].(map[string]interface{})
if !ok {
params = make(map[string]interface{})
pubExt["params"] = params
}
params["networkId"] = networkID
Comment on lines +278 to +283
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is this right? You're not doing anything with params after modifying it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I was under the impression that since maps are reference types, extracting params here points to the same underlying data structure as pubExt['params']. So, modifying params update pubExt without needing a reassignment.

It seems to be working as expected in my tests, but if you find this flow unclear, please suggest me a clearer way i'll be happy to implement it.


updated, err := jsonutil.Marshal(pubExt)
if err != nil {
return nil, &errortypes.BadInput{
Message: fmt.Sprintf("Error marshaling %s: %s", fieldPath, err),
}
}
return updated, nil
}

func (a *adapter) buildEndpointURL(networkId string, siteDomain string, appDomain string, bundle string) (string, error) {
parsed, err := url.Parse(a.endpoint)
if err != nil {
return "", fmt.Errorf("invalid endpoint URL %q: %w", a.endpoint, err)
}

hasSite := siteDomain != ""
hasAppOrBundle := appDomain != "" || bundle != ""

var b strings.Builder

b.WriteString("network_id=")
b.WriteString(url.QueryEscape(networkId))

if hasSite {
v := siteDomain
if v == "" {
v = unknownValue
}
b.WriteString("&site_domain=")
b.WriteString(url.QueryEscape(v))
} else if hasAppOrBundle {
ad := appDomain
if ad == "" {
ad = unknownValue
}
bd := bundle
if bd == "" {
bd = unknownValue
}

b.WriteString("&app_domain=")
b.WriteString(url.QueryEscape(ad))
b.WriteString("&bundle=")
b.WriteString(url.QueryEscape(bd))
}

parsed.RawQuery = b.String()
return parsed.String(), nil
}

func (a *adapter) MakeBids(req *openrtb2.BidRequest, reqData *adapters.RequestData, respData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
if adapters.IsResponseStatusCodeNoContent(respData) {
return nil, nil
Expand Down
Loading
Loading