github.com/prebid/prebid-server/v2@v2.18.0/usersync/syncersbuilder.go (about)

     1  package usersync
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strings"
     7  
     8  	"github.com/prebid/prebid-server/v2/config"
     9  )
    10  
    11  type namedSyncerConfig struct {
    12  	name string
    13  	cfg  config.Syncer
    14  }
    15  
    16  // SyncerBuildError represents an error with building a syncer.
    17  type SyncerBuildError struct {
    18  	Bidder    string
    19  	SyncerKey string
    20  	Err       error
    21  }
    22  
    23  // Error implements the standard error interface.
    24  func (e SyncerBuildError) Error() string {
    25  	return fmt.Sprintf("cannot create syncer for bidder %s with key %s: %v", e.Bidder, e.SyncerKey, e.Err)
    26  }
    27  
    28  func BuildSyncers(hostConfig *config.Configuration, bidderInfos config.BidderInfos) (map[string]Syncer, []error) {
    29  	// map syncer config by bidder
    30  	cfgByBidder := make(map[string]config.Syncer, len(bidderInfos))
    31  	for bidder, cfg := range bidderInfos {
    32  		if shouldCreateSyncer(cfg) {
    33  			cfgByBidder[bidder] = *cfg.Syncer
    34  		}
    35  	}
    36  
    37  	// map syncer config by key
    38  	cfgBySyncerKey := make(map[string][]namedSyncerConfig, len(bidderInfos))
    39  	for bidder, cfg := range cfgByBidder {
    40  		if cfg.Key == "" {
    41  			cfg.Key = bidder
    42  		}
    43  		cfgBySyncerKey[cfg.Key] = append(cfgBySyncerKey[cfg.Key], namedSyncerConfig{bidder, cfg})
    44  	}
    45  
    46  	// resolve host endpoint
    47  	hostUserSyncConfig := hostConfig.UserSync
    48  	if hostUserSyncConfig.ExternalURL == "" {
    49  		hostUserSyncConfig.ExternalURL = hostConfig.ExternalURL
    50  	}
    51  
    52  	// create syncers
    53  	errs := []error{}
    54  	syncers := make(map[string]Syncer, len(bidderInfos))
    55  	for key, cfgGroup := range cfgBySyncerKey {
    56  		primaryCfg, err := chooseSyncerConfig(cfgGroup)
    57  		if err != nil {
    58  			errs = append(errs, err)
    59  			continue
    60  		}
    61  
    62  		for _, bidder := range cfgGroup {
    63  			syncer, err := NewSyncer(hostUserSyncConfig, primaryCfg.cfg, bidder.name)
    64  			if err != nil {
    65  				errs = append(errs, SyncerBuildError{
    66  					Bidder:    primaryCfg.name,
    67  					SyncerKey: key,
    68  					Err:       err,
    69  				})
    70  				continue
    71  			}
    72  			syncers[bidder.name] = syncer
    73  		}
    74  	}
    75  
    76  	if len(errs) > 0 {
    77  		return nil, errs
    78  	}
    79  	return syncers, nil
    80  }
    81  
    82  func shouldCreateSyncer(cfg config.BidderInfo) bool {
    83  	if cfg.Disabled {
    84  		return false
    85  	}
    86  
    87  	if cfg.Syncer == nil {
    88  		return false
    89  	}
    90  
    91  	// a syncer may provide just a Supports field to provide hints to the host. we should only try to create a syncer
    92  	// if there is at least one non-Supports value populated.
    93  	return cfg.Syncer.Key != "" || cfg.Syncer.IFrame != nil || cfg.Syncer.Redirect != nil || cfg.Syncer.SupportCORS != nil
    94  }
    95  
    96  func chooseSyncerConfig(biddersSyncerConfig []namedSyncerConfig) (namedSyncerConfig, error) {
    97  	if len(biddersSyncerConfig) == 1 {
    98  		return biddersSyncerConfig[0], nil
    99  	}
   100  
   101  	var bidderNames []string
   102  	var bidderNamesWithEndpoints []string
   103  	var syncerConfig namedSyncerConfig
   104  	for _, bidder := range biddersSyncerConfig {
   105  		bidderNames = append(bidderNames, bidder.name)
   106  		if bidder.cfg.IFrame != nil || bidder.cfg.Redirect != nil {
   107  			bidderNamesWithEndpoints = append(bidderNamesWithEndpoints, bidder.name)
   108  			syncerConfig = bidder
   109  		}
   110  	}
   111  
   112  	if len(bidderNamesWithEndpoints) == 0 {
   113  		sort.Strings(bidderNames)
   114  		bidders := strings.Join(bidderNames, ", ")
   115  		return namedSyncerConfig{}, fmt.Errorf("bidders %s share the same syncer key, but none define endpoints (iframe and/or redirect)", bidders)
   116  	}
   117  
   118  	if len(bidderNamesWithEndpoints) > 1 {
   119  		sort.Strings(bidderNamesWithEndpoints)
   120  		bidders := strings.Join(bidderNamesWithEndpoints, ", ")
   121  		return namedSyncerConfig{}, fmt.Errorf("bidders %s define endpoints (iframe and/or redirect) for the same syncer key, but only one bidder is permitted to define endpoints", bidders)
   122  	}
   123  
   124  	return syncerConfig, nil
   125  }