github.com/prebid/prebid-server/v2@v2.18.0/hooks/plan.go (about)

     1  package hooks
     2  
     3  import (
     4  	"time"
     5  
     6  	"github.com/golang/glog"
     7  	"github.com/prebid/prebid-server/v2/config"
     8  	"github.com/prebid/prebid-server/v2/hooks/hookstage"
     9  )
    10  
    11  type Stage string
    12  
    13  // Names of the available stages.
    14  const (
    15  	StageEntrypoint               Stage = "entrypoint"
    16  	StageRawAuctionRequest        Stage = "raw_auction_request"
    17  	StageProcessedAuctionRequest  Stage = "processed_auction_request"
    18  	StageBidderRequest            Stage = "bidder_request"
    19  	StageRawBidderResponse        Stage = "raw_bidder_response"
    20  	StageAllProcessedBidResponses Stage = "all_processed_bid_responses"
    21  	StageAuctionResponse          Stage = "auction_response"
    22  )
    23  
    24  func (s Stage) String() string {
    25  	return string(s)
    26  }
    27  
    28  func (s Stage) IsRejectable() bool {
    29  	return s != StageAllProcessedBidResponses &&
    30  		s != StageAuctionResponse
    31  }
    32  
    33  // ExecutionPlanBuilder is the interface that provides methods
    34  // for retrieving hooks grouped and sorted in the established order
    35  // according to the hook execution plan intended for run at a certain stage.
    36  type ExecutionPlanBuilder interface {
    37  	PlanForEntrypointStage(endpoint string) Plan[hookstage.Entrypoint]
    38  	PlanForRawAuctionStage(endpoint string, account *config.Account) Plan[hookstage.RawAuctionRequest]
    39  	PlanForProcessedAuctionStage(endpoint string, account *config.Account) Plan[hookstage.ProcessedAuctionRequest]
    40  	PlanForBidderRequestStage(endpoint string, account *config.Account) Plan[hookstage.BidderRequest]
    41  	PlanForRawBidderResponseStage(endpoint string, account *config.Account) Plan[hookstage.RawBidderResponse]
    42  	PlanForAllProcessedBidResponsesStage(endpoint string, account *config.Account) Plan[hookstage.AllProcessedBidResponses]
    43  	PlanForAuctionResponseStage(endpoint string, account *config.Account) Plan[hookstage.AuctionResponse]
    44  }
    45  
    46  // Plan represents a slice of groups of hooks of a specific type grouped in the established order.
    47  type Plan[T any] []Group[T]
    48  
    49  // Group represents a slice of hooks sorted in the established order.
    50  type Group[T any] struct {
    51  	// Timeout specifies the max duration in milliseconds that a group of hooks is allowed to run.
    52  	Timeout time.Duration
    53  	// Hooks holds a slice of HookWrapper of a specific type.
    54  	Hooks []HookWrapper[T]
    55  }
    56  
    57  // HookWrapper wraps Hook representing specific hook interface
    58  // and holds additional meta information, such as Module name and hook Code.
    59  type HookWrapper[T any] struct {
    60  	// Module holds a name of the module that provides the Hook.
    61  	// Specified in the format "vendor.module_name".
    62  	Module string
    63  	// Code is an arbitrary value assigned to hook via the hook execution plan
    64  	// and is used when sending metrics, logging debug information, etc.
    65  	Code string
    66  	// Hook is an instance of the specific hook interface.
    67  	Hook T
    68  }
    69  
    70  // NewExecutionPlanBuilder returns a new instance of the ExecutionPlanBuilder interface.
    71  // Depending on the hooks' status, method returns a real PlanBuilder or the EmptyPlanBuilder.
    72  func NewExecutionPlanBuilder(hooks config.Hooks, repo HookRepository) ExecutionPlanBuilder {
    73  	if hooks.Enabled {
    74  		return PlanBuilder{
    75  			hooks: hooks,
    76  			repo:  repo,
    77  		}
    78  	}
    79  	return EmptyPlanBuilder{}
    80  }
    81  
    82  // PlanBuilder is a concrete implementation of the ExecutionPlanBuilder interface.
    83  // Which returns hook execution plans for specific stage defined by the hook config.
    84  type PlanBuilder struct {
    85  	hooks config.Hooks
    86  	repo  HookRepository
    87  }
    88  
    89  func (p PlanBuilder) PlanForEntrypointStage(endpoint string) Plan[hookstage.Entrypoint] {
    90  	return getMergedPlan(
    91  		p.hooks,
    92  		nil,
    93  		endpoint,
    94  		StageEntrypoint,
    95  		p.repo.GetEntrypointHook,
    96  	)
    97  }
    98  
    99  func (p PlanBuilder) PlanForRawAuctionStage(endpoint string, account *config.Account) Plan[hookstage.RawAuctionRequest] {
   100  	return getMergedPlan(
   101  		p.hooks,
   102  		account,
   103  		endpoint,
   104  		StageRawAuctionRequest,
   105  		p.repo.GetRawAuctionHook,
   106  	)
   107  }
   108  
   109  func (p PlanBuilder) PlanForProcessedAuctionStage(endpoint string, account *config.Account) Plan[hookstage.ProcessedAuctionRequest] {
   110  	return getMergedPlan(
   111  		p.hooks,
   112  		account,
   113  		endpoint,
   114  		StageProcessedAuctionRequest,
   115  		p.repo.GetProcessedAuctionHook,
   116  	)
   117  }
   118  
   119  func (p PlanBuilder) PlanForBidderRequestStage(endpoint string, account *config.Account) Plan[hookstage.BidderRequest] {
   120  	return getMergedPlan(
   121  		p.hooks,
   122  		account,
   123  		endpoint,
   124  		StageBidderRequest,
   125  		p.repo.GetBidderRequestHook,
   126  	)
   127  }
   128  
   129  func (p PlanBuilder) PlanForRawBidderResponseStage(endpoint string, account *config.Account) Plan[hookstage.RawBidderResponse] {
   130  	return getMergedPlan(
   131  		p.hooks,
   132  		account,
   133  		endpoint,
   134  		StageRawBidderResponse,
   135  		p.repo.GetRawBidderResponseHook,
   136  	)
   137  }
   138  
   139  func (p PlanBuilder) PlanForAllProcessedBidResponsesStage(endpoint string, account *config.Account) Plan[hookstage.AllProcessedBidResponses] {
   140  	return getMergedPlan(
   141  		p.hooks,
   142  		account,
   143  		endpoint,
   144  		StageAllProcessedBidResponses,
   145  		p.repo.GetAllProcessedBidResponsesHook,
   146  	)
   147  }
   148  
   149  func (p PlanBuilder) PlanForAuctionResponseStage(endpoint string, account *config.Account) Plan[hookstage.AuctionResponse] {
   150  	return getMergedPlan(
   151  		p.hooks,
   152  		account,
   153  		endpoint,
   154  		StageAuctionResponse,
   155  		p.repo.GetAuctionResponseHook,
   156  	)
   157  }
   158  
   159  type hookFn[T any] func(moduleName string) (T, bool)
   160  
   161  func getMergedPlan[T any](
   162  	cfg config.Hooks,
   163  	account *config.Account,
   164  	endpoint string,
   165  	stage Stage,
   166  	getHookFn hookFn[T],
   167  ) Plan[T] {
   168  	accountPlan := cfg.DefaultAccountExecutionPlan
   169  	if account != nil && account.Hooks.ExecutionPlan.Endpoints != nil {
   170  		accountPlan = account.Hooks.ExecutionPlan
   171  	}
   172  
   173  	plan := getPlan(getHookFn, cfg.HostExecutionPlan, endpoint, stage)
   174  	plan = append(plan, getPlan(getHookFn, accountPlan, endpoint, stage)...)
   175  
   176  	return plan
   177  }
   178  
   179  func getPlan[T any](getHookFn hookFn[T], cfg config.HookExecutionPlan, endpoint string, stage Stage) Plan[T] {
   180  	plan := make(Plan[T], 0, len(cfg.Endpoints[endpoint].Stages[stage.String()].Groups))
   181  	for _, groupCfg := range cfg.Endpoints[endpoint].Stages[stage.String()].Groups {
   182  		group := getGroup(getHookFn, groupCfg)
   183  		if len(group.Hooks) > 0 {
   184  			plan = append(plan, group)
   185  		}
   186  	}
   187  
   188  	return plan
   189  }
   190  
   191  func getGroup[T any](getHookFn hookFn[T], cfg config.HookExecutionGroup) Group[T] {
   192  	group := Group[T]{
   193  		Timeout: time.Duration(cfg.Timeout) * time.Millisecond,
   194  		Hooks:   make([]HookWrapper[T], 0, len(cfg.HookSequence)),
   195  	}
   196  
   197  	for _, hookCfg := range cfg.HookSequence {
   198  		if h, ok := getHookFn(hookCfg.ModuleCode); ok {
   199  			group.Hooks = append(group.Hooks, HookWrapper[T]{Module: hookCfg.ModuleCode, Code: hookCfg.HookImplCode, Hook: h})
   200  		} else {
   201  			glog.Warningf("Not found hook while building hook execution plan: %s %s", hookCfg.ModuleCode, hookCfg.HookImplCode)
   202  		}
   203  	}
   204  
   205  	return group
   206  }