use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use ferridriver::error::Result;
use crate::filter::TagExpression;
use crate::step::StepLocation;
use crate::world::BrowserWorld;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum HookPoint {
BeforeAll,
AfterAll,
BeforeFeature,
AfterFeature,
BeforeScenario,
AfterScenario,
BeforeStep,
AfterStep,
}
pub enum HookHandler {
Global(Arc<dyn Fn() -> Pin<Box<dyn Future<Output = Result<()>> + Send>> + Send + Sync>),
Scenario(
Arc<dyn for<'a> Fn(&'a mut BrowserWorld) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>> + Send + Sync>,
),
Step(
Arc<
dyn for<'a> Fn(&'a mut BrowserWorld, &'a str) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>
+ Send
+ Sync,
>,
),
}
pub struct Hook {
pub point: HookPoint,
pub tag_filter: Option<TagExpression>,
pub order: i32,
pub handler: HookHandler,
pub location: StepLocation,
}
pub struct HookRegistry {
hooks: Vec<Hook>,
}
impl HookRegistry {
pub fn new() -> Self {
Self { hooks: Vec::new() }
}
pub fn register(&mut self, hook: Hook) {
self.hooks.push(hook);
}
pub fn get(&self, point: HookPoint, tags: &[String]) -> Vec<&Hook> {
let mut matched: Vec<&Hook> = self
.hooks
.iter()
.filter(|h| h.point == point)
.filter(|h| match &h.tag_filter {
None => true,
Some(expr) => expr.matches(tags),
})
.collect();
matched.sort_by_key(|h| h.order);
matched
}
pub fn get_global(&self, point: HookPoint) -> Vec<&Hook> {
let mut matched: Vec<&Hook> = self.hooks.iter().filter(|h| h.point == point).collect();
matched.sort_by_key(|h| h.order);
matched
}
pub async fn run_global(&self, point: HookPoint) -> Result<()> {
for hook in self.get_global(point) {
if let HookHandler::Global(handler) = &hook.handler {
handler().await?;
}
}
Ok(())
}
pub async fn run_scenario(&self, point: HookPoint, world: &mut BrowserWorld, tags: &[String]) -> Result<()> {
for hook in self.get(point, tags) {
if let HookHandler::Scenario(handler) = &hook.handler {
handler(world).await?;
}
}
Ok(())
}
pub async fn run_suite(&self, point: HookPoint, world: &mut BrowserWorld, tags: &[String]) -> Result<()> {
for hook in self.get(point, tags) {
match &hook.handler {
HookHandler::Scenario(handler) => handler(world).await?,
HookHandler::Global(handler) => handler().await?,
HookHandler::Step(_) => {},
}
}
Ok(())
}
pub async fn run_step(
&self,
point: HookPoint,
world: &mut BrowserWorld,
step_text: &str,
tags: &[String],
) -> Result<()> {
for hook in self.get(point, tags) {
match &hook.handler {
HookHandler::Step(handler) => handler(world, step_text).await?,
HookHandler::Scenario(handler) => handler(world).await?,
_ => {},
}
}
Ok(())
}
}
impl Default for HookRegistry {
fn default() -> Self {
Self::new()
}
}
pub fn runtime_hook_point(registration: &ferridriver_test::HookRegistration) -> Option<HookPoint> {
match (registration.phase, registration.scope) {
(ferridriver_test::HookPhase::Before, ferridriver_test::HookScope::Suite) => Some(HookPoint::BeforeAll),
(ferridriver_test::HookPhase::After, ferridriver_test::HookScope::Suite) => Some(HookPoint::AfterAll),
(ferridriver_test::HookPhase::Before, ferridriver_test::HookScope::Scenario) => Some(HookPoint::BeforeScenario),
(ferridriver_test::HookPhase::After, ferridriver_test::HookScope::Scenario) => Some(HookPoint::AfterScenario),
(ferridriver_test::HookPhase::Before, ferridriver_test::HookScope::Step) => Some(HookPoint::BeforeStep),
(ferridriver_test::HookPhase::After, ferridriver_test::HookScope::Step) => Some(HookPoint::AfterStep),
}
}
pub struct HookRegistration {
pub point: HookPoint,
pub tag_filter: Option<String>,
pub order: i32,
pub handler_factory: fn() -> HookHandler,
pub file: &'static str,
pub line: u32,
}
inventory::collect!(HookRegistration);
#[macro_export]
macro_rules! submit_hook {
($name:ident, $point:expr, $tag_filter:expr, $order:expr, $handler:ident,) => {
ferridriver_bdd::inventory::submit! {
ferridriver_bdd::hook::HookRegistration {
point: $point,
tag_filter: $tag_filter,
order: $order,
handler_factory: $handler,
file: file!(),
line: line!(),
}
}
};
}