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
229 changes: 175 additions & 54 deletions adapters/sparteo/sparteo.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package sparteo
import (
"fmt"
"net/http"
"net/url"
"strings"

"github.com/prebid/openrtb/v20/openrtb2"
"github.com/prebid/prebid-server/v3/adapters"
Expand All @@ -13,19 +15,20 @@ import (
)

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 +50,20 @@ 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 != nil && req.Site.Publisher != nil {
publisherCopy := *req.Site.Publisher
request.Site.Publisher = &publisherCopy
}

var errs []error
var siteNetworkId string

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 @@ -86,63 +77,102 @@ func (a *adapter) MakeRequests(req *openrtb2.BidRequest, reqInfo *adapters.Extra
sparteoMap = make(map[string]interface{})
extMap["sparteo"] = sparteoMap
}

paramsMap, ok := sparteoMap["params"].(map[string]interface{})
if !ok {
paramsMap = make(map[string]interface{})
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
}
}

updatedExt, err := jsonutil.Marshal(extMap)
if err != nil {
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 sb strings.Builder
sb.WriteString("network_id=")
sb.WriteString(url.QueryEscape(networkID))

var pubToUpdate *openrtb2.Publisher
var pubExtPath string

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

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{})
pubToUpdate = ensurePublisher(request.Site.Publisher)
request.Site.Publisher = pubToUpdate
pubExtPath = "site.publisher.ext"

domain := resolveSiteDomain(request.Site)
if domain == "" {
domain = unknownValue
errs = append(errs, &errortypes.BadInput{
Message: "Domain not found. Missing the site.domain or the site.page field",
})
}
sb.WriteString("&site_domain=")
sb.WriteString(url.QueryEscape(domain))
} else if req.App != nil {
appCopy := *req.App
request.App = &appCopy
if req.App.Publisher != nil {
pubCopy := *req.App.Publisher
request.App.Publisher = &pubCopy
}

paramsMap["networkId"] = siteNetworkId
pubExt["params"] = paramsMap
pubToUpdate = ensurePublisher(request.App.Publisher)
request.App.Publisher = pubToUpdate
pubExtPath = "app.publisher.ext"

updatedPubExt, err := jsonutil.Marshal(pubExt)
if err != nil {
appDomain := resolveAppDomain(request.App)
if appDomain == "" {
appDomain = unknownValue
}
Comment on lines +140 to +142
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this correct? I see that when req.Site is set and domain is empty an error is recorded on lines 121-123 but no error is recorded here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the app_domain is helpful yet optional (a 'nice-to-have'). The critical mandatory field for Apps is actually the bundle, which we do validate and return an error for a few lines further down (lines 148-152). If you are ok with it, I'd prefer to keep the logic as is.

sb.WriteString("&app_domain=")
sb.WriteString(url.QueryEscape(appDomain))

bundle := resolveBundle(request.App)
if bundle == "" {
bundle = unknownValue
errs = append(errs, &errortypes.BadInput{
Message: fmt.Sprintf("Error marshaling site.publisher.ext: %s", err),
Message: "Bundle not found. Missing the app.bundle field.",
})
} else {
request.Site.Publisher.Ext = jsonutil.RawMessage(updatedPubExt)
}
sb.WriteString("&bundle=")
sb.WriteString(url.QueryEscape(bundle))
} else {
// NO CONTEXT (Fallback)
request.Site = &openrtb2.Site{}
pubToUpdate = ensurePublisher(request.Site.Publisher)
request.Site.Publisher = pubToUpdate
pubExtPath = "site.publisher.ext"
}

ext, err := updatePublisherExtension(&pubToUpdate.Ext, networkID, pubExtPath)
if err != nil {
errs = append(errs, err)
} else {
pubToUpdate.Ext = ext
}

uri, err := url.Parse(a.endpoint)
if err != nil {
return nil, []error{fmt.Errorf("invalid endpoint URL %q: %w", a.endpoint, err)}
}
uri.RawQuery = sb.String()

body, err := jsonutil.Marshal(request)
if err != nil {
errs = append(errs, err)
Expand All @@ -151,7 +181,7 @@ func (a *adapter) MakeRequests(req *openrtb2.BidRequest, reqInfo *adapters.Extra

requestData := &adapters.RequestData{
Method: http.MethodPost,
Uri: a.endpoint,
Uri: uri.String(),
Body: body,
ImpIDs: openrtb_ext.GetImpIDs(request.Imp),
Headers: http.Header{
Expand All @@ -162,6 +192,97 @@ 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 d
}
if fromPage := normalizeHostname(site.Page); fromPage != "" {
return fromPage
}
}
return ""
}

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

func resolveBundle(app *openrtb2.App) string {
if app == nil {
return ""
}
raw := strings.TrimSpace(app.Bundle)
if raw == "" || strings.EqualFold(raw, "null") {
return ""
}
return raw
}

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 +270 to +275
Copy link
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
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) MakeBids(req *openrtb2.BidRequest, reqData *adapters.RequestData, respData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
if adapters.IsResponseStatusCodeNoContent(respData) {
return nil, nil
Expand Down
Loading
Loading