use std::fmt;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use regex::Regex;
use crate::world::BrowserWorld;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize)]
pub enum StepKind {
Given,
When,
Then,
Step,
}
impl fmt::Display for StepKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Given => write!(f, "Given"),
Self::When => write!(f, "When"),
Self::Then => write!(f, "Then"),
Self::Step => write!(f, "Step"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum StepParam {
String(String),
Int(i64),
Float(f64),
Word(String),
Custom { type_name: String, value: String },
}
impl StepParam {
pub fn as_string(&self) -> Option<String> {
match self {
Self::String(s) | Self::Word(s) => Some(s.clone()),
Self::Int(i) => Some(i.to_string()),
Self::Float(f) => Some(f.to_string()),
Self::Custom { value, .. } => Some(value.clone()),
}
}
pub fn as_int(&self) -> Option<i64> {
match self {
Self::Int(i) => Some(*i),
Self::String(s) | Self::Word(s) => s.parse().ok(),
Self::Float(f) => Some(*f as i64),
Self::Custom { value, .. } => value.parse().ok(),
}
}
pub fn as_float(&self) -> Option<f64> {
match self {
Self::Float(f) => Some(*f),
Self::Int(i) => Some(*i as f64),
Self::String(s) | Self::Word(s) => s.parse().ok(),
Self::Custom { value, .. } => value.parse().ok(),
}
}
}
pub use crate::data_table::DataTable;
#[derive(Debug, Clone)]
pub struct StepError {
pub message: String,
pub diff: Option<(String, String)>,
pub pending: bool,
}
impl StepError {
pub fn pending(message: impl Into<String>) -> Self {
Self {
message: message.into(),
diff: None,
pending: true,
}
}
#[must_use]
pub fn wrap(prefix: impl std::fmt::Display, err: ferridriver::FerriError) -> Self {
Self {
message: format!("{prefix}: {}", err.display_named()),
diff: None,
pending: false,
}
}
}
impl From<ferridriver::FerriError> for StepError {
fn from(err: ferridriver::FerriError) -> Self {
Self {
message: err.display_named(),
diff: None,
pending: false,
}
}
}
impl From<ferridriver_expect::AssertionFailure> for StepError {
fn from(err: ferridriver_expect::AssertionFailure) -> Self {
Self {
message: err.message,
diff: err.diff.map(|d| (d, String::new())),
pending: false,
}
}
}
impl fmt::Display for StepError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)?;
if let Some((expected, actual)) = &self.diff {
write!(f, "\n expected: {expected}\n actual: {actual}")?;
}
Ok(())
}
}
impl std::error::Error for StepError {}
impl From<String> for StepError {
fn from(message: String) -> Self {
Self {
message,
diff: None,
pending: false,
}
}
}
impl From<&str> for StepError {
fn from(message: &str) -> Self {
Self {
message: message.to_string(),
diff: None,
pending: false,
}
}
}
pub type StepHandler = Arc<
dyn for<'a> Fn(
&'a mut BrowserWorld,
Vec<StepParam>,
Option<&'a DataTable>,
Option<&'a str>,
) -> Pin<Box<dyn Future<Output = Result<(), StepError>> + Send + 'a>>
+ Send
+ Sync,
>;
#[derive(Debug, Clone)]
pub struct StepLocation {
pub file: &'static str,
pub line: u32,
}
impl fmt::Display for StepLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.file, self.line)
}
}
pub struct StepDef {
pub kind: StepKind,
pub expression: String,
pub regex: Regex,
pub param_types: Vec<crate::expression::ParamType>,
pub param_infos: Vec<crate::expression::ParamInfo>,
pub handler: StepHandler,
pub location: StepLocation,
}
pub struct StepMatch<'a> {
pub def: &'a StepDef,
pub params: Vec<StepParam>,
}
#[derive(Debug)]
pub enum MatchError {
Undefined { text: String, suggestions: Vec<String> },
Ambiguous {
text: String,
matches: Vec<StepLocation>,
expressions: Vec<String>,
},
}
impl fmt::Display for MatchError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Undefined { text, suggestions } => {
write!(f, "undefined step: \"{text}\"")?;
if !suggestions.is_empty() {
write!(f, "\n did you mean:")?;
for s in suggestions {
write!(f, "\n - {s}")?;
}
}
Ok(())
},
Self::Ambiguous {
text,
matches,
expressions,
} => {
write!(f, "ambiguous step: \"{text}\" matched {} definitions:", matches.len())?;
for (i, (loc, expr)) in matches.iter().zip(expressions.iter()).enumerate() {
write!(f, "\n {}. {} ({})", i + 1, expr, loc)?;
}
Ok(())
},
}
}
}
impl std::error::Error for MatchError {}
pub struct StepRegistration {
pub kind: StepKind,
pub expression: &'static str,
pub handler_factory: fn() -> StepHandler,
pub file: &'static str,
pub line: u32,
pub is_regex: bool,
}
inventory::collect!(StepRegistration);
#[macro_export]
macro_rules! submit_step {
($name:ident, $kind:expr, $expr:expr, $handler:ident,) => {
ferridriver_bdd::inventory::submit! {
ferridriver_bdd::step::StepRegistration {
kind: $kind,
expression: $expr,
handler_factory: $handler,
file: file!(),
line: line!(),
is_regex: false,
}
}
};
($name:ident, $kind:expr, $expr:expr, $handler:ident, regex = $is_regex:expr,) => {
ferridriver_bdd::inventory::submit! {
ferridriver_bdd::step::StepRegistration {
kind: $kind,
expression: $expr,
handler_factory: $handler,
file: file!(),
line: line!(),
is_regex: $is_regex,
}
}
};
}