github.com/prebid/prebid-server/v2@v2.18.0/config/bidderinfo.go (about)

     1  package config
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"log"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  	"text/template"
    11  
    12  	"github.com/prebid/prebid-server/v2/macros"
    13  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    14  
    15  	validator "github.com/asaskevich/govalidator"
    16  	"gopkg.in/yaml.v3"
    17  )
    18  
    19  // BidderInfos contains a mapping of bidder name to bidder info.
    20  type BidderInfos map[string]BidderInfo
    21  
    22  // BidderInfo specifies all configuration for a bidder except for enabled status, endpoint, and extra information.
    23  type BidderInfo struct {
    24  	AliasOf          string `yaml:"aliasOf" mapstructure:"aliasOf"`
    25  	Disabled         bool   `yaml:"disabled" mapstructure:"disabled"`
    26  	Endpoint         string `yaml:"endpoint" mapstructure:"endpoint"`
    27  	ExtraAdapterInfo string `yaml:"extra_info" mapstructure:"extra_info"`
    28  
    29  	Maintainer              *MaintainerInfo   `yaml:"maintainer" mapstructure:"maintainer"`
    30  	Capabilities            *CapabilitiesInfo `yaml:"capabilities" mapstructure:"capabilities"`
    31  	ModifyingVastXmlAllowed bool              `yaml:"modifyingVastXmlAllowed" mapstructure:"modifyingVastXmlAllowed"`
    32  	Debug                   *DebugInfo        `yaml:"debug" mapstructure:"debug"`
    33  	GVLVendorID             uint16            `yaml:"gvlVendorID" mapstructure:"gvlVendorID"`
    34  
    35  	Syncer *Syncer `yaml:"userSync" mapstructure:"userSync"`
    36  
    37  	Experiment BidderInfoExperiment `yaml:"experiment" mapstructure:"experiment"`
    38  
    39  	// needed for Rubicon
    40  	XAPI AdapterXAPI `yaml:"xapi" mapstructure:"xapi"`
    41  
    42  	// needed for Facebook
    43  	PlatformID string `yaml:"platform_id" mapstructure:"platform_id"`
    44  	AppSecret  string `yaml:"app_secret" mapstructure:"app_secret"`
    45  	// EndpointCompression determines, if set, the type of compression the bid request will undergo before being sent to the corresponding bid server
    46  	EndpointCompression string       `yaml:"endpointCompression" mapstructure:"endpointCompression"`
    47  	OpenRTB             *OpenRTBInfo `yaml:"openrtb" mapstructure:"openrtb"`
    48  }
    49  
    50  type aliasNillableFields struct {
    51  	Disabled                *bool                 `yaml:"disabled" mapstructure:"disabled"`
    52  	ModifyingVastXmlAllowed *bool                 `yaml:"modifyingVastXmlAllowed" mapstructure:"modifyingVastXmlAllowed"`
    53  	Experiment              *BidderInfoExperiment `yaml:"experiment" mapstructure:"experiment"`
    54  	XAPI                    *AdapterXAPI          `yaml:"xapi" mapstructure:"xapi"`
    55  }
    56  
    57  // BidderInfoExperiment specifies non-production ready feature config for a bidder
    58  type BidderInfoExperiment struct {
    59  	AdsCert BidderAdsCert `yaml:"adsCert" mapstructure:"adsCert"`
    60  }
    61  
    62  // BidderAdsCert enables Call Sign feature for bidder
    63  type BidderAdsCert struct {
    64  	Enabled bool `yaml:"enabled" mapstructure:"enabled"`
    65  }
    66  
    67  // MaintainerInfo specifies the support email address for a bidder.
    68  type MaintainerInfo struct {
    69  	Email string `yaml:"email" mapstructure:"email"`
    70  }
    71  
    72  // CapabilitiesInfo specifies the supported platforms for a bidder.
    73  type CapabilitiesInfo struct {
    74  	App  *PlatformInfo `yaml:"app" mapstructure:"app"`
    75  	Site *PlatformInfo `yaml:"site" mapstructure:"site"`
    76  	DOOH *PlatformInfo `yaml:"dooh" mapstructure:"dooh"`
    77  }
    78  
    79  // PlatformInfo specifies the supported media types for a bidder.
    80  type PlatformInfo struct {
    81  	MediaTypes []openrtb_ext.BidType `yaml:"mediaTypes" mapstructure:"mediaTypes"`
    82  }
    83  
    84  // DebugInfo specifies the supported debug options for a bidder.
    85  type DebugInfo struct {
    86  	Allow bool `yaml:"allow" mapstructure:"allow"`
    87  }
    88  
    89  type AdapterXAPI struct {
    90  	Username string `yaml:"username" mapstructure:"username"`
    91  	Password string `yaml:"password" mapstructure:"password"`
    92  	Tracker  string `yaml:"tracker" mapstructure:"tracker"`
    93  }
    94  
    95  // OpenRTBInfo specifies the versions/aspects of openRTB that a bidder supports
    96  // Version is not yet actively supported
    97  // GPPSupported is not yet actively supported
    98  type OpenRTBInfo struct {
    99  	Version      string `yaml:"version" mapstructure:"version"`
   100  	GPPSupported bool   `yaml:"gpp-supported" mapstructure:"gpp-supported"`
   101  }
   102  
   103  // Syncer specifies the user sync settings for a bidder. This struct is shared by the account config,
   104  // so it needs to have both yaml and mapstructure mappings.
   105  type Syncer struct {
   106  	// Key is used as the record key for the user sync cookie. We recommend using the bidder name
   107  	// as the key for consistency, but that is not enforced as a requirement.
   108  	Key string `yaml:"key" mapstructure:"key"`
   109  
   110  	// Supports allows bidders to specify which user sync endpoints they support but which don't have
   111  	// good defaults. Host companies should contact the bidder for the endpoint configuration. Hosts
   112  	// may not override this value.
   113  	Supports []string `yaml:"supports" mapstructure:"supports"`
   114  
   115  	// IFrame configures an iframe endpoint for user syncing.
   116  	IFrame *SyncerEndpoint `yaml:"iframe" mapstructure:"iframe"`
   117  
   118  	// Redirect configures an redirect endpoint for user syncing. This is also known as an image
   119  	// endpoint in the Prebid.js project.
   120  	Redirect *SyncerEndpoint `yaml:"redirect" mapstructure:"redirect"`
   121  
   122  	// ExternalURL is available as a macro to the RedirectURL template.
   123  	ExternalURL string `yaml:"externalUrl" mapstructure:"external_url"`
   124  
   125  	// SupportCORS identifies if CORS is supported for the user syncing endpoints.
   126  	SupportCORS *bool `yaml:"supportCors" mapstructure:"support_cors"`
   127  
   128  	// FormatOverride allows a bidder to override their callback type "b" for iframe, "i" for redirect
   129  	FormatOverride string `yaml:"formatOverride" mapstructure:"format_override"`
   130  
   131  	// Enabled signifies whether a bidder is enabled/disabled for user sync
   132  	Enabled *bool `yaml:"enabled" mapstructure:"enabled"`
   133  
   134  	// SkipWhen allows bidders to specify when they don't want to sync
   135  	SkipWhen *SkipWhen `yaml:"skipwhen" mapstructure:"skipwhen"`
   136  }
   137  
   138  type SkipWhen struct {
   139  	GDPR   bool     `yaml:"gdpr" mapstructure:"gdpr"`
   140  	GPPSID []string `yaml:"gpp_sid" mapstructure:"gpp_sid"`
   141  }
   142  
   143  // SyncerEndpoint specifies the configuration of the URL returned by the /cookie_sync endpoint
   144  // for a specific bidder. Bidders must specify at least one endpoint configuration to be eligible
   145  // for selection during a user sync request.
   146  //
   147  // URL is the only required field, although we highly recommend to use the available macros to
   148  // make the configuration readable and maintainable. User sync urls include a redirect url back to
   149  // Prebid Server which is url escaped and can be very diffcult for humans to read.
   150  //
   151  // In most cases, bidders will specify a URL with a `{{.RedirectURL}}` macro for the call back to
   152  // Prebid Server and a UserMacro which the bidder server will replace with the user's id. Example:
   153  //
   154  //	url: "https://sync.bidderserver.com/usersync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redirect={{.RedirectURL}}"
   155  //	userMacro: "$UID"
   156  //
   157  // Prebid Server is configured with a default RedirectURL template matching the /setuid call. This
   158  // may be overridden for all bidders with the `user_sync.redirect_url` host configuration or for a
   159  // specific bidder with the RedirectURL value in this struct.
   160  type SyncerEndpoint struct {
   161  	// URL is the endpoint on the bidder server the user will be redirected to when a user sync is
   162  	// requested. The following macros are resolved at application startup:
   163  	//
   164  	//  {{.RedirectURL}} - This will be replaced with a redirect url generated using the RedirectURL
   165  	//                     template and url escaped for safe inclusion in any part of the URL.
   166  	//
   167  	// The following macros are specific to individual requests and are resolved at runtime using the
   168  	// Go template engine. For more information on Go templates, see: https://golang.org/pkg/text/template/
   169  	//
   170  	//  {{.GDPR}}        - This will be replaced with the "gdpr" property sent to /cookie_sync.
   171  	//  {{.Consent}}     - This will be replaced with the "consent" property sent to /cookie_sync.
   172  	//  {{.USPrivacy}}   - This will be replaced with the "us_privacy" property sent to /cookie_sync.
   173  	//  {{.GPP}}		 - This will be replaced with the "gpp" property sent to /cookie_sync.
   174  	//  {{.GPPSID}}		 - This will be replaced with the "gpp_sid" property sent to /cookie_sync.
   175  	URL string `yaml:"url" mapstructure:"url"`
   176  
   177  	// RedirectURL is an endpoint on the host server the user will be redirected to when a user sync
   178  	// request has been completed by the bidder server. The following macros are resolved at application
   179  	// startup:
   180  	//
   181  	//  {{.ExternalURL}} - This will be replaced with the host server's externally reachable http path.
   182  	//  {{.BidderName}}  - This will be replaced with the bidder name.
   183  	//  {{.SyncType}}    - This will be replaced with the sync type, either 'b' for iframe syncs or 'i'
   184  	//                     for redirect/image syncs.
   185  	//  {{.UserMacro}}   - This will be replaced with the bidder server's user id macro.
   186  	//
   187  	// The endpoint on the host server is usually Prebid Server's /setuid endpoint. The default value is:
   188  	// `{{.ExternalURL}}/setuid?bidder={{.SyncerKey}}&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&f={{.SyncType}}&uid={{.UserMacro}}`
   189  	RedirectURL string `yaml:"redirectUrl" mapstructure:"redirect_url"`
   190  
   191  	// ExternalURL is available as a macro to the RedirectURL template. If not specified, either the syncer configuration
   192  	// value or the host configuration value is used.
   193  	ExternalURL string `yaml:"externalUrl" mapstructure:"external_url"`
   194  
   195  	// UserMacro is available as a macro to the RedirectURL template. This value is specific to the bidder server
   196  	// and has no default.
   197  	UserMacro string `yaml:"userMacro" mapstructure:"user_macro"`
   198  }
   199  
   200  func (bi BidderInfo) IsEnabled() bool {
   201  	return !bi.Disabled
   202  }
   203  
   204  type InfoReader interface {
   205  	Read() (map[string][]byte, error)
   206  }
   207  
   208  type InfoReaderFromDisk struct {
   209  	Path string
   210  }
   211  
   212  const (
   213  	SyncResponseFormatIFrame   = "b" // b = blank HTML response
   214  	SyncResponseFormatRedirect = "i" // i = image response
   215  )
   216  
   217  func (r InfoReaderFromDisk) Read() (map[string][]byte, error) {
   218  	bidderConfigs, err := os.ReadDir(r.Path)
   219  	if err != nil {
   220  		log.Fatal(err)
   221  	}
   222  
   223  	bidderInfos := make(map[string][]byte)
   224  	for _, bidderConfig := range bidderConfigs {
   225  		if bidderConfig.IsDir() {
   226  			continue //ignore directories
   227  		}
   228  		fileName := bidderConfig.Name()
   229  		filePath := filepath.Join(r.Path, fileName)
   230  		data, err := os.ReadFile(filePath)
   231  		if err != nil {
   232  			return nil, err
   233  		}
   234  		bidderInfos[fileName] = data
   235  	}
   236  	return bidderInfos, nil
   237  }
   238  
   239  func LoadBidderInfoFromDisk(path string) (BidderInfos, error) {
   240  	bidderInfoReader := InfoReaderFromDisk{Path: path}
   241  	return LoadBidderInfo(bidderInfoReader)
   242  }
   243  
   244  func LoadBidderInfo(reader InfoReader) (BidderInfos, error) {
   245  	return processBidderInfos(reader, openrtb_ext.NormalizeBidderName)
   246  }
   247  
   248  func processBidderInfos(reader InfoReader, normalizeBidderName func(string) (openrtb_ext.BidderName, bool)) (BidderInfos, error) {
   249  	bidderConfigs, err := reader.Read()
   250  	if err != nil {
   251  		return nil, fmt.Errorf("error loading bidders data")
   252  	}
   253  
   254  	bidderInfos := BidderInfos{}
   255  	aliasNillableFieldsByBidder := map[string]aliasNillableFields{}
   256  	for fileName, data := range bidderConfigs {
   257  		bidderName := strings.Split(fileName, ".")
   258  		if len(bidderName) == 2 && bidderName[1] == "yaml" {
   259  			info := BidderInfo{}
   260  			if err := yaml.Unmarshal(data, &info); err != nil {
   261  				return nil, fmt.Errorf("error parsing config for bidder %s: %v", fileName, err)
   262  			}
   263  
   264  			//need to maintain nullable fields from BidderInfo struct into bidderInfoNullableFields
   265  			//to handle the default values in aliases yaml
   266  			if len(info.AliasOf) > 0 {
   267  				aliasFields := aliasNillableFields{}
   268  				if err := yaml.Unmarshal(data, &aliasFields); err != nil {
   269  					return nil, fmt.Errorf("error parsing config for aliased bidder %s: %v", fileName, err)
   270  				}
   271  
   272  				//required for CoreBidderNames function to also return aliasBiddernames
   273  				if err := openrtb_ext.SetAliasBidderName(bidderName[0], openrtb_ext.BidderName(info.AliasOf)); err != nil {
   274  					return nil, err
   275  				}
   276  
   277  				normalizedBidderName, bidderNameExists := normalizeBidderName(bidderName[0])
   278  				if !bidderNameExists {
   279  					return nil, fmt.Errorf("error parsing config for an alias %s: unknown bidder", fileName)
   280  				}
   281  
   282  				aliasNillableFieldsByBidder[string(normalizedBidderName)] = aliasFields
   283  				bidderInfos[string(normalizedBidderName)] = info
   284  			} else {
   285  				normalizedBidderName, bidderNameExists := normalizeBidderName(bidderName[0])
   286  				if !bidderNameExists {
   287  					return nil, fmt.Errorf("error parsing config for bidder %s: unknown bidder", fileName)
   288  				}
   289  
   290  				bidderInfos[string(normalizedBidderName)] = info
   291  			}
   292  		}
   293  	}
   294  	return processBidderAliases(aliasNillableFieldsByBidder, bidderInfos)
   295  }
   296  
   297  func processBidderAliases(aliasNillableFieldsByBidder map[string]aliasNillableFields, bidderInfos BidderInfos) (BidderInfos, error) {
   298  	for bidderName, alias := range aliasNillableFieldsByBidder {
   299  		aliasBidderInfo, ok := bidderInfos[bidderName]
   300  		if !ok {
   301  			return nil, fmt.Errorf("bidder info not found for an alias: %s", bidderName)
   302  		}
   303  		if err := validateAliases(aliasBidderInfo, bidderInfos, bidderName); err != nil {
   304  			return nil, err
   305  		}
   306  
   307  		parentBidderInfo := bidderInfos[aliasBidderInfo.AliasOf]
   308  		if aliasBidderInfo.AppSecret == "" {
   309  			aliasBidderInfo.AppSecret = parentBidderInfo.AppSecret
   310  		}
   311  		if aliasBidderInfo.Capabilities == nil {
   312  			aliasBidderInfo.Capabilities = parentBidderInfo.Capabilities
   313  		}
   314  		if aliasBidderInfo.Debug == nil {
   315  			aliasBidderInfo.Debug = parentBidderInfo.Debug
   316  		}
   317  		if aliasBidderInfo.Endpoint == "" {
   318  			aliasBidderInfo.Endpoint = parentBidderInfo.Endpoint
   319  		}
   320  		if aliasBidderInfo.EndpointCompression == "" {
   321  			aliasBidderInfo.EndpointCompression = parentBidderInfo.EndpointCompression
   322  		}
   323  		if aliasBidderInfo.ExtraAdapterInfo == "" {
   324  			aliasBidderInfo.ExtraAdapterInfo = parentBidderInfo.ExtraAdapterInfo
   325  		}
   326  		if aliasBidderInfo.GVLVendorID == 0 {
   327  			aliasBidderInfo.GVLVendorID = parentBidderInfo.GVLVendorID
   328  		}
   329  		if aliasBidderInfo.Maintainer == nil {
   330  			aliasBidderInfo.Maintainer = parentBidderInfo.Maintainer
   331  		}
   332  		if aliasBidderInfo.OpenRTB == nil {
   333  			aliasBidderInfo.OpenRTB = parentBidderInfo.OpenRTB
   334  		}
   335  		if aliasBidderInfo.PlatformID == "" {
   336  			aliasBidderInfo.PlatformID = parentBidderInfo.PlatformID
   337  		}
   338  		if aliasBidderInfo.Syncer == nil && parentBidderInfo.Syncer != nil {
   339  			syncerKey := aliasBidderInfo.AliasOf
   340  			if parentBidderInfo.Syncer.Key != "" {
   341  				syncerKey = parentBidderInfo.Syncer.Key
   342  			}
   343  			syncer := Syncer{Key: syncerKey}
   344  			aliasBidderInfo.Syncer = &syncer
   345  		}
   346  		if alias.Disabled == nil {
   347  			aliasBidderInfo.Disabled = parentBidderInfo.Disabled
   348  		}
   349  		if alias.Experiment == nil {
   350  			aliasBidderInfo.Experiment = parentBidderInfo.Experiment
   351  		}
   352  		if alias.ModifyingVastXmlAllowed == nil {
   353  			aliasBidderInfo.ModifyingVastXmlAllowed = parentBidderInfo.ModifyingVastXmlAllowed
   354  		}
   355  		if alias.XAPI == nil {
   356  			aliasBidderInfo.XAPI = parentBidderInfo.XAPI
   357  		}
   358  		bidderInfos[bidderName] = aliasBidderInfo
   359  	}
   360  	return bidderInfos, nil
   361  }
   362  
   363  // ToGVLVendorIDMap transforms a BidderInfos object to a map of bidder names to GVL id.
   364  // Disabled bidders are omitted from the result.
   365  func (infos BidderInfos) ToGVLVendorIDMap() map[openrtb_ext.BidderName]uint16 {
   366  	gvlVendorIds := make(map[openrtb_ext.BidderName]uint16, len(infos))
   367  	for name, info := range infos {
   368  		if info.IsEnabled() && info.GVLVendorID != 0 {
   369  			gvlVendorIds[openrtb_ext.BidderName(name)] = info.GVLVendorID
   370  		}
   371  	}
   372  	return gvlVendorIds
   373  }
   374  
   375  // validateBidderInfos validates bidder endpoint, info and syncer data
   376  func (infos BidderInfos) validate(errs []error) []error {
   377  	for bidderName, bidder := range infos {
   378  		if bidder.IsEnabled() {
   379  			errs = validateAdapterEndpoint(bidder.Endpoint, bidderName, errs)
   380  
   381  			if err := validateInfo(bidder, infos, bidderName); err != nil {
   382  				errs = append(errs, err)
   383  			}
   384  
   385  			if err := validateSyncer(bidder); err != nil {
   386  				errs = append(errs, err)
   387  			}
   388  		}
   389  	}
   390  	return errs
   391  }
   392  
   393  func validateAliases(aliasBidderInfo BidderInfo, infos BidderInfos, bidderName string) error {
   394  	if len(aliasBidderInfo.AliasOf) > 0 {
   395  		if parentBidder, ok := infos[aliasBidderInfo.AliasOf]; ok {
   396  			if len(parentBidder.AliasOf) > 0 {
   397  				return fmt.Errorf("bidder: %s cannot be an alias of an alias: %s", aliasBidderInfo.AliasOf, bidderName)
   398  			}
   399  		} else {
   400  			return fmt.Errorf("bidder: %s not found for an alias: %s", aliasBidderInfo.AliasOf, bidderName)
   401  		}
   402  	}
   403  	return nil
   404  }
   405  
   406  var testEndpointTemplateParams = macros.EndpointTemplateParams{
   407  	Host:        "anyHost",
   408  	PublisherID: "anyPublisherID",
   409  	AccountID:   "anyAccountID",
   410  	ZoneID:      "anyZoneID",
   411  	SourceId:    "anySourceID",
   412  	AdUnit:      "anyAdUnit",
   413  	MediaType:   "MediaType",
   414  }
   415  
   416  // validateAdapterEndpoint makes sure that an adapter has a valid endpoint
   417  // associated with it
   418  func validateAdapterEndpoint(endpoint string, bidderName string, errs []error) []error {
   419  	if endpoint == "" {
   420  		return append(errs, fmt.Errorf("There's no default endpoint available for %s. Calls to this bidder/exchange will fail. "+
   421  			"Please set adapters.%s.endpoint in your app config", bidderName, bidderName))
   422  	}
   423  
   424  	// Create endpoint template
   425  	endpointTemplate, err := template.New("endpointTemplate").Parse(endpoint)
   426  	if err != nil {
   427  		return append(errs, fmt.Errorf("Invalid endpoint template: %s for adapter: %s. %v", endpoint, bidderName, err))
   428  	}
   429  	// Resolve macros (if any) in the endpoint URL
   430  	resolvedEndpoint, err := macros.ResolveMacros(endpointTemplate, testEndpointTemplateParams)
   431  	if err != nil {
   432  		return append(errs, fmt.Errorf("Unable to resolve endpoint: %s for adapter: %s. %v", endpoint, bidderName, err))
   433  	}
   434  	// Validate the resolved endpoint
   435  	//
   436  	// Validating using both IsURL and IsRequestURL because IsURL allows relative paths
   437  	// whereas IsRequestURL requires absolute path but fails to check other valid URL
   438  	// format constraints.
   439  	//
   440  	// For example: IsURL will allow "abcd.com" but IsRequestURL won't
   441  	// IsRequestURL will allow "http://http://abcd.com" but IsURL won't
   442  	if !validator.IsURL(resolvedEndpoint) || !validator.IsRequestURL(resolvedEndpoint) {
   443  		errs = append(errs, fmt.Errorf("The endpoint: %s for %s is not a valid URL", resolvedEndpoint, bidderName))
   444  	}
   445  	return errs
   446  }
   447  
   448  func validateInfo(bidder BidderInfo, infos BidderInfos, bidderName string) error {
   449  	if err := validateMaintainer(bidder.Maintainer, bidderName); err != nil {
   450  		return err
   451  	}
   452  	if err := validateCapabilities(bidder.Capabilities, bidderName); err != nil {
   453  		return err
   454  	}
   455  	if len(bidder.AliasOf) > 0 {
   456  		if err := validateAliasCapabilities(bidder, infos, bidderName); err != nil {
   457  			return err
   458  		}
   459  	}
   460  	return nil
   461  }
   462  
   463  func validateMaintainer(info *MaintainerInfo, bidderName string) error {
   464  	if info == nil || info.Email == "" {
   465  		return fmt.Errorf("missing required field: maintainer.email for adapter: %s", bidderName)
   466  	}
   467  	return nil
   468  }
   469  
   470  func validateAliasCapabilities(aliasBidderInfo BidderInfo, infos BidderInfos, bidderName string) error {
   471  	parentBidder, parentFound := infos[aliasBidderInfo.AliasOf]
   472  	if !parentFound {
   473  		return fmt.Errorf("parent bidder: %s not found for an alias: %s", aliasBidderInfo.AliasOf, bidderName)
   474  	}
   475  
   476  	if aliasBidderInfo.Capabilities != nil {
   477  		if parentBidder.Capabilities == nil {
   478  			return fmt.Errorf("capabilities for alias: %s should be a subset of capabilities for parent bidder: %s", bidderName, aliasBidderInfo.AliasOf)
   479  		}
   480  
   481  		if (aliasBidderInfo.Capabilities.App != nil && parentBidder.Capabilities.App == nil) ||
   482  			(aliasBidderInfo.Capabilities.Site != nil && parentBidder.Capabilities.Site == nil) ||
   483  			(aliasBidderInfo.Capabilities.DOOH != nil && parentBidder.Capabilities.DOOH == nil) {
   484  			return fmt.Errorf("capabilities for alias: %s should be a subset of capabilities for parent bidder: %s", bidderName, aliasBidderInfo.AliasOf)
   485  		}
   486  
   487  		if aliasBidderInfo.Capabilities.Site != nil && parentBidder.Capabilities.Site != nil {
   488  			if err := isAliasPlatformInfoSubsetOfParent(*parentBidder.Capabilities.Site, *aliasBidderInfo.Capabilities.Site, bidderName, aliasBidderInfo.AliasOf); err != nil {
   489  				return err
   490  			}
   491  		}
   492  
   493  		if aliasBidderInfo.Capabilities.App != nil && parentBidder.Capabilities.App != nil {
   494  			if err := isAliasPlatformInfoSubsetOfParent(*parentBidder.Capabilities.App, *aliasBidderInfo.Capabilities.App, bidderName, aliasBidderInfo.AliasOf); err != nil {
   495  				return err
   496  			}
   497  		}
   498  
   499  		if aliasBidderInfo.Capabilities.DOOH != nil && parentBidder.Capabilities.DOOH != nil {
   500  			if err := isAliasPlatformInfoSubsetOfParent(*parentBidder.Capabilities.DOOH, *aliasBidderInfo.Capabilities.DOOH, bidderName, aliasBidderInfo.AliasOf); err != nil {
   501  				return err
   502  			}
   503  		}
   504  	}
   505  
   506  	return nil
   507  }
   508  
   509  func isAliasPlatformInfoSubsetOfParent(parentInfo PlatformInfo, aliasInfo PlatformInfo, bidderName string, parentBidderName string) error {
   510  	parentMediaTypes := make(map[openrtb_ext.BidType]struct{})
   511  	for _, info := range parentInfo.MediaTypes {
   512  		parentMediaTypes[info] = struct{}{}
   513  	}
   514  
   515  	for _, info := range aliasInfo.MediaTypes {
   516  		if _, found := parentMediaTypes[info]; !found {
   517  			return fmt.Errorf("mediaTypes for alias: %s should be a subset of MediaTypes for parent bidder: %s", bidderName, parentBidderName)
   518  		}
   519  	}
   520  
   521  	return nil
   522  }
   523  
   524  func validateCapabilities(info *CapabilitiesInfo, bidderName string) error {
   525  	if info == nil {
   526  		return fmt.Errorf("missing required field: capabilities for adapter: %s", bidderName)
   527  	}
   528  
   529  	if info.App == nil && info.Site == nil && info.DOOH == nil {
   530  		return fmt.Errorf("at least one of capabilities.site, capabilities.app, or capabilities.dooh must exist for adapter: %s", bidderName)
   531  	}
   532  
   533  	if info.App != nil {
   534  		if err := validatePlatformInfo(info.App); err != nil {
   535  			return fmt.Errorf("capabilities.app failed validation: %v for adapter: %s", err, bidderName)
   536  		}
   537  	}
   538  
   539  	if info.Site != nil {
   540  		if err := validatePlatformInfo(info.Site); err != nil {
   541  			return fmt.Errorf("capabilities.site failed validation: %v for adapter: %s", err, bidderName)
   542  		}
   543  	}
   544  
   545  	if info.DOOH != nil {
   546  		if err := validatePlatformInfo(info.DOOH); err != nil {
   547  			return fmt.Errorf("capabilities.dooh failed validation: %v for adapter: %s", err, bidderName)
   548  		}
   549  	}
   550  
   551  	return nil
   552  }
   553  
   554  func validatePlatformInfo(info *PlatformInfo) error {
   555  	if len(info.MediaTypes) == 0 {
   556  		return errors.New("at least one media type needs to be specified")
   557  	}
   558  
   559  	for index, mediaType := range info.MediaTypes {
   560  		if mediaType != "banner" && mediaType != "video" && mediaType != "native" && mediaType != "audio" {
   561  			return fmt.Errorf("unrecognized media type at index %d: %s", index, mediaType)
   562  		}
   563  	}
   564  
   565  	return nil
   566  }
   567  
   568  func validateSyncer(bidderInfo BidderInfo) error {
   569  	if bidderInfo.Syncer == nil {
   570  		return nil
   571  	}
   572  
   573  	if bidderInfo.Syncer.FormatOverride != SyncResponseFormatIFrame && bidderInfo.Syncer.FormatOverride != SyncResponseFormatRedirect && bidderInfo.Syncer.FormatOverride != "" {
   574  		return fmt.Errorf("syncer could not be created, invalid format override value: %s", bidderInfo.Syncer.FormatOverride)
   575  	}
   576  
   577  	for _, supports := range bidderInfo.Syncer.Supports {
   578  		if !strings.EqualFold(supports, "iframe") && !strings.EqualFold(supports, "redirect") {
   579  			return fmt.Errorf("syncer could not be created, invalid supported endpoint: %s", supports)
   580  		}
   581  	}
   582  
   583  	return nil
   584  }
   585  
   586  func applyBidderInfoConfigOverrides(configBidderInfos nillableFieldBidderInfos, fsBidderInfos BidderInfos, normalizeBidderName func(string) (openrtb_ext.BidderName, bool)) (BidderInfos, error) {
   587  	mergedBidderInfos := make(map[string]BidderInfo, len(fsBidderInfos))
   588  
   589  	for bidderName, configBidderInfo := range configBidderInfos {
   590  		normalizedBidderName, exists := normalizeBidderName(bidderName)
   591  		if !exists {
   592  			return nil, fmt.Errorf("error setting configuration for bidder %s: unknown bidder", bidderName)
   593  		}
   594  		fsBidderInfo, exists := fsBidderInfos[string(normalizedBidderName)]
   595  		if !exists {
   596  			return nil, fmt.Errorf("error finding configuration for bidder %s: unknown bidder", bidderName)
   597  		}
   598  
   599  		mergedBidderInfo := fsBidderInfo
   600  		mergedBidderInfo.Syncer = configBidderInfo.bidderInfo.Syncer.Override(fsBidderInfo.Syncer)
   601  		if len(configBidderInfo.bidderInfo.Endpoint) > 0 {
   602  			mergedBidderInfo.Endpoint = configBidderInfo.bidderInfo.Endpoint
   603  		}
   604  		if len(configBidderInfo.bidderInfo.ExtraAdapterInfo) > 0 {
   605  			mergedBidderInfo.ExtraAdapterInfo = configBidderInfo.bidderInfo.ExtraAdapterInfo
   606  		}
   607  		if configBidderInfo.bidderInfo.Maintainer != nil {
   608  			mergedBidderInfo.Maintainer = configBidderInfo.bidderInfo.Maintainer
   609  		}
   610  		if configBidderInfo.bidderInfo.Capabilities != nil {
   611  			mergedBidderInfo.Capabilities = configBidderInfo.bidderInfo.Capabilities
   612  		}
   613  		if configBidderInfo.bidderInfo.Debug != nil {
   614  			mergedBidderInfo.Debug = configBidderInfo.bidderInfo.Debug
   615  		}
   616  		if configBidderInfo.bidderInfo.GVLVendorID > 0 {
   617  			mergedBidderInfo.GVLVendorID = configBidderInfo.bidderInfo.GVLVendorID
   618  		}
   619  		if configBidderInfo.bidderInfo.XAPI.Username != "" {
   620  			mergedBidderInfo.XAPI.Username = configBidderInfo.bidderInfo.XAPI.Username
   621  		}
   622  		if configBidderInfo.bidderInfo.XAPI.Password != "" {
   623  			mergedBidderInfo.XAPI.Password = configBidderInfo.bidderInfo.XAPI.Password
   624  		}
   625  		if configBidderInfo.bidderInfo.XAPI.Tracker != "" {
   626  			mergedBidderInfo.XAPI.Tracker = configBidderInfo.bidderInfo.XAPI.Tracker
   627  		}
   628  		if configBidderInfo.bidderInfo.PlatformID != "" {
   629  			mergedBidderInfo.PlatformID = configBidderInfo.bidderInfo.PlatformID
   630  		}
   631  		if configBidderInfo.bidderInfo.AppSecret != "" {
   632  			mergedBidderInfo.AppSecret = configBidderInfo.bidderInfo.AppSecret
   633  		}
   634  		if configBidderInfo.nillableFields.Disabled != nil {
   635  			mergedBidderInfo.Disabled = configBidderInfo.bidderInfo.Disabled
   636  		}
   637  		if configBidderInfo.nillableFields.ModifyingVastXmlAllowed != nil {
   638  			mergedBidderInfo.ModifyingVastXmlAllowed = configBidderInfo.bidderInfo.ModifyingVastXmlAllowed
   639  		}
   640  		if configBidderInfo.bidderInfo.Experiment.AdsCert.Enabled == true {
   641  			mergedBidderInfo.Experiment.AdsCert.Enabled = true
   642  		}
   643  		if configBidderInfo.bidderInfo.EndpointCompression != "" {
   644  			mergedBidderInfo.EndpointCompression = configBidderInfo.bidderInfo.EndpointCompression
   645  		}
   646  		if configBidderInfo.bidderInfo.OpenRTB != nil {
   647  			mergedBidderInfo.OpenRTB = configBidderInfo.bidderInfo.OpenRTB
   648  		}
   649  
   650  		mergedBidderInfos[string(normalizedBidderName)] = mergedBidderInfo
   651  	}
   652  	for bidderName, fsBidderInfo := range fsBidderInfos {
   653  		if _, exists := mergedBidderInfos[bidderName]; !exists {
   654  			mergedBidderInfos[bidderName] = fsBidderInfo
   655  		}
   656  	}
   657  
   658  	return mergedBidderInfos, nil
   659  }
   660  
   661  // Override returns a new Syncer object where values in the original are replaced by non-empty/non-default
   662  // values in the override, except for the Supports field which may not be overridden. No changes are made
   663  // to the original or override Syncer.
   664  func (s *Syncer) Override(original *Syncer) *Syncer {
   665  	if s == nil && original == nil {
   666  		return nil
   667  	}
   668  
   669  	var copy Syncer
   670  	if original != nil {
   671  		copy = *original
   672  	}
   673  
   674  	if s == nil {
   675  		return &copy
   676  	}
   677  
   678  	if s.Key != "" {
   679  		copy.Key = s.Key
   680  	}
   681  
   682  	if original == nil {
   683  		copy.IFrame = s.IFrame.Override(nil)
   684  		copy.Redirect = s.Redirect.Override(nil)
   685  	} else {
   686  		copy.IFrame = s.IFrame.Override(original.IFrame)
   687  		copy.Redirect = s.Redirect.Override(original.Redirect)
   688  	}
   689  
   690  	if s.ExternalURL != "" {
   691  		copy.ExternalURL = s.ExternalURL
   692  	}
   693  
   694  	if s.SupportCORS != nil {
   695  		copy.SupportCORS = s.SupportCORS
   696  	}
   697  
   698  	return &copy
   699  }
   700  
   701  // Override returns a new SyncerEndpoint object where values in the original are replaced by non-empty/non-default
   702  // values in the override. No changes are made to the original or override SyncerEndpoint.
   703  func (s *SyncerEndpoint) Override(original *SyncerEndpoint) *SyncerEndpoint {
   704  	if s == nil && original == nil {
   705  		return nil
   706  	}
   707  
   708  	var copy SyncerEndpoint
   709  	if original != nil {
   710  		copy = *original
   711  	}
   712  
   713  	if s == nil {
   714  		return &copy
   715  	}
   716  
   717  	if s.URL != "" {
   718  		copy.URL = s.URL
   719  	}
   720  
   721  	if s.RedirectURL != "" {
   722  		copy.RedirectURL = s.RedirectURL
   723  	}
   724  
   725  	if s.ExternalURL != "" {
   726  		copy.ExternalURL = s.ExternalURL
   727  	}
   728  
   729  	if s.UserMacro != "" {
   730  		copy.UserMacro = s.UserMacro
   731  	}
   732  
   733  	return &copy
   734  }