use std::any::Any;
use std::panic::{catch_unwind, AssertUnwindSafe};
use crate::scenario::{Scenario, ScenarioContext};
use crate::types::StepResult;
type StepFunc = dyn Fn(&ScenarioContext, bool) -> StepResult;
pub struct ScenarioStep {
step_text: String,
location: &'static str,
func: Box<StepFunc>,
reg: Box<dyn Fn(&Scenario)>,
}
impl ScenarioStep {
pub fn new<F, R>(step_text: String, func: F, reg: R, location: &'static str) -> Self
where
F: Fn(&ScenarioContext, bool) -> StepResult + 'static,
R: Fn(&Scenario) + 'static,
{
Self {
step_text,
location,
func: Box::new(func),
reg: Box::new(reg),
}
}
fn render_panic(location: &str, name: &str, err: Box<dyn Any + Send>) -> String {
if let Some(msg) = err.downcast_ref::<&str>() {
format!("{location}: step {name} panic'd: {msg}")
} else if let Some(msg) = err.downcast_ref::<String>() {
format!("{location}: step {name} panic'd: {msg}")
} else {
format!("{location}: step {name} panic'd")
}
}
pub fn call(&self, context: &ScenarioContext, defuse_poison: bool) -> StepResult {
let func = AssertUnwindSafe(|| (*self.func)(context, defuse_poison));
catch_unwind(func).map_err(|e| Self::render_panic(self.location(), self.step_text(), e))?
}
pub fn step_text(&self) -> &str {
&self.step_text
}
pub(crate) fn register_contexts(&self, scenario: &Scenario) {
(*self.reg)(scenario);
}
pub(crate) fn location(&self) -> &'static str {
self.location
}
}