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 }