1use std::future::Future;
4use std::pin::Pin;
5use std::sync::Arc;
6
7use ferridriver::error::Result;
8
9use crate::filter::TagExpression;
10use crate::step::StepLocation;
11use crate::world::BrowserWorld;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub enum HookPoint {
18 BeforeAll,
19 AfterAll,
20 BeforeFeature,
21 AfterFeature,
22 BeforeScenario,
23 AfterScenario,
24 BeforeStep,
25 AfterStep,
26}
27
28pub enum HookHandler {
32 Global(Arc<dyn Fn() -> Pin<Box<dyn Future<Output = Result<()>> + Send>> + Send + Sync>),
34 Scenario(
36 Arc<dyn for<'a> Fn(&'a mut BrowserWorld) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>> + Send + Sync>,
37 ),
38 Step(
40 Arc<
41 dyn for<'a> Fn(&'a mut BrowserWorld, &'a str) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>
42 + Send
43 + Sync,
44 >,
45 ),
46}
47
48pub struct Hook {
52 pub point: HookPoint,
54 pub tag_filter: Option<TagExpression>,
57 pub order: i32,
59 pub handler: HookHandler,
61 pub location: StepLocation,
63}
64
65pub struct HookRegistry {
69 hooks: Vec<Hook>,
70}
71
72impl HookRegistry {
73 pub fn new() -> Self {
74 Self { hooks: Vec::new() }
75 }
76
77 pub fn register(&mut self, hook: Hook) {
79 self.hooks.push(hook);
80 }
81
82 pub fn get(&self, point: HookPoint, tags: &[String]) -> Vec<&Hook> {
84 let mut matched: Vec<&Hook> = self
85 .hooks
86 .iter()
87 .filter(|h| h.point == point)
88 .filter(|h| match &h.tag_filter {
89 None => true,
90 Some(expr) => expr.matches(tags),
91 })
92 .collect();
93
94 matched.sort_by_key(|h| h.order);
95 matched
96 }
97
98 pub fn get_global(&self, point: HookPoint) -> Vec<&Hook> {
100 let mut matched: Vec<&Hook> = self.hooks.iter().filter(|h| h.point == point).collect();
101 matched.sort_by_key(|h| h.order);
102 matched
103 }
104
105 pub async fn run_global(&self, point: HookPoint) -> Result<()> {
107 for hook in self.get_global(point) {
108 if let HookHandler::Global(handler) = &hook.handler {
109 handler().await?;
110 }
111 }
112 Ok(())
113 }
114
115 pub async fn run_scenario(&self, point: HookPoint, world: &mut BrowserWorld, tags: &[String]) -> Result<()> {
117 for hook in self.get(point, tags) {
118 if let HookHandler::Scenario(handler) = &hook.handler {
119 handler(world).await?;
120 }
121 }
122 Ok(())
123 }
124
125 pub async fn run_suite(&self, point: HookPoint, world: &mut BrowserWorld, tags: &[String]) -> Result<()> {
130 for hook in self.get(point, tags) {
131 match &hook.handler {
132 HookHandler::Scenario(handler) => handler(world).await?,
133 HookHandler::Global(handler) => handler().await?,
134 HookHandler::Step(_) => {},
135 }
136 }
137 Ok(())
138 }
139
140 pub async fn run_step(
142 &self,
143 point: HookPoint,
144 world: &mut BrowserWorld,
145 step_text: &str,
146 tags: &[String],
147 ) -> Result<()> {
148 for hook in self.get(point, tags) {
149 match &hook.handler {
150 HookHandler::Step(handler) => handler(world, step_text).await?,
151 HookHandler::Scenario(handler) => handler(world).await?,
152 _ => {},
153 }
154 }
155 Ok(())
156 }
157}
158
159impl Default for HookRegistry {
160 fn default() -> Self {
161 Self::new()
162 }
163}
164
165pub fn runtime_hook_point(registration: &ferridriver_test::HookRegistration) -> Option<HookPoint> {
166 match (registration.phase, registration.scope) {
167 (ferridriver_test::HookPhase::Before, ferridriver_test::HookScope::Suite) => Some(HookPoint::BeforeAll),
168 (ferridriver_test::HookPhase::After, ferridriver_test::HookScope::Suite) => Some(HookPoint::AfterAll),
169 (ferridriver_test::HookPhase::Before, ferridriver_test::HookScope::Scenario) => Some(HookPoint::BeforeScenario),
170 (ferridriver_test::HookPhase::After, ferridriver_test::HookScope::Scenario) => Some(HookPoint::AfterScenario),
171 (ferridriver_test::HookPhase::Before, ferridriver_test::HookScope::Step) => Some(HookPoint::BeforeStep),
172 (ferridriver_test::HookPhase::After, ferridriver_test::HookScope::Step) => Some(HookPoint::AfterStep),
173 }
174}
175
176pub struct HookRegistration {
180 pub point: HookPoint,
181 pub tag_filter: Option<String>,
182 pub order: i32,
183 pub handler_factory: fn() -> HookHandler,
184 pub file: &'static str,
185 pub line: u32,
186}
187
188inventory::collect!(HookRegistration);
189
190#[macro_export]
192macro_rules! submit_hook {
193 ($name:ident, $point:expr, $tag_filter:expr, $order:expr, $handler:ident,) => {
194 ferridriver_bdd::inventory::submit! {
195 ferridriver_bdd::hook::HookRegistration {
196 point: $point,
197 tag_filter: $tag_filter,
198 order: $order,
199 handler_factory: $handler,
200 file: file!(),
201 line: line!(),
202 }
203 }
204 };
205}