use std::{any::Any, fmt, sync::Arc};
#[cfg(feature = "timestamps")]
use std::time::SystemTime;
use derive_more::{AsRef, Deref, DerefMut, Display, Error, From};
use crate::{step, writer::basic::coerce_error};
pub type Info = Arc<dyn Any + Send + 'static>;
#[derive(AsRef, Clone, Copy, Debug, Deref, DerefMut)]
#[non_exhaustive]
pub struct Event<T: ?Sized> {
#[cfg(feature = "timestamps")]
pub at: SystemTime,
#[as_ref]
#[deref]
#[deref_mut]
pub value: T,
}
impl<T> Event<T> {
#[must_use]
pub fn new(value: T) -> Self {
Self {
#[cfg(feature = "timestamps")]
at: SystemTime::now(),
value,
}
}
#[allow(clippy::missing_const_for_fn)] #[must_use]
pub fn into_inner(self) -> T {
self.value
}
#[must_use]
pub fn split(self) -> (T, Metadata) {
self.replace(())
}
#[must_use]
pub fn insert<V>(self, value: V) -> Event<V> {
self.replace(value).1
}
#[must_use]
pub fn map<V>(self, f: impl FnOnce(T) -> V) -> Event<V> {
let (val, meta) = self.split();
meta.insert(f(val))
}
#[allow(clippy::missing_const_for_fn)] #[must_use]
pub fn replace<V>(self, value: V) -> (T, Event<V>) {
let event = Event {
#[cfg(feature = "timestamps")]
at: self.at,
value,
};
(self.value, event)
}
}
pub type Metadata = Event<()>;
impl Metadata {
#[must_use]
pub fn wrap<V>(self, value: V) -> Event<V> {
self.replace(value).1
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Retries {
pub current: usize,
pub left: usize,
}
impl Retries {
#[must_use]
pub const fn initial(left: usize) -> Self {
Self { left, current: 0 }
}
#[must_use]
pub fn next_try(self) -> Option<Self> {
self.left.checked_sub(1).map(|left| Self {
left,
current: self.current + 1,
})
}
}
#[allow(variant_size_differences)]
#[derive(Debug)]
pub enum Cucumber<World> {
Started,
Feature(Arc<gherkin::Feature>, Feature<World>),
ParsingFinished {
features: usize,
rules: usize,
scenarios: usize,
steps: usize,
parser_errors: usize,
},
Finished,
}
impl<World> Clone for Cucumber<World> {
fn clone(&self) -> Self {
match self {
Self::Started => Self::Started,
Self::Feature(f, ev) => Self::Feature(Arc::clone(f), ev.clone()),
Self::ParsingFinished {
features,
rules,
scenarios,
steps,
parser_errors,
} => Self::ParsingFinished {
features: *features,
rules: *rules,
scenarios: *scenarios,
steps: *steps,
parser_errors: *parser_errors,
},
Self::Finished => Self::Finished,
}
}
}
impl<World> Cucumber<World> {
#[must_use]
pub fn feature_started(feat: Arc<gherkin::Feature>) -> Self {
Self::Feature(feat, Feature::Started)
}
#[must_use]
pub fn rule_started(
feat: Arc<gherkin::Feature>,
rule: Arc<gherkin::Rule>,
) -> Self {
Self::Feature(feat, Feature::Rule(rule, Rule::Started))
}
#[must_use]
pub fn feature_finished(feat: Arc<gherkin::Feature>) -> Self {
Self::Feature(feat, Feature::Finished)
}
#[must_use]
pub fn rule_finished(
feat: Arc<gherkin::Feature>,
rule: Arc<gherkin::Rule>,
) -> Self {
Self::Feature(feat, Feature::Rule(rule, Rule::Finished))
}
#[must_use]
pub fn scenario(
feat: Arc<gherkin::Feature>,
rule: Option<Arc<gherkin::Rule>>,
scenario: Arc<gherkin::Scenario>,
event: RetryableScenario<World>,
) -> Self {
Self::Feature(
feat,
if let Some(r) = rule {
Feature::Rule(r, Rule::Scenario(scenario, event))
} else {
Feature::Scenario(scenario, event)
},
)
}
}
#[derive(Debug)]
pub enum Feature<World> {
Started,
Rule(Arc<gherkin::Rule>, Rule<World>),
Scenario(Arc<gherkin::Scenario>, RetryableScenario<World>),
Finished,
}
impl<World> Clone for Feature<World> {
fn clone(&self) -> Self {
match self {
Self::Started => Self::Started,
Self::Rule(r, ev) => Self::Rule(Arc::clone(r), ev.clone()),
Self::Scenario(s, ev) => Self::Scenario(Arc::clone(s), ev.clone()),
Self::Finished => Self::Finished,
}
}
}
#[derive(Debug)]
pub enum Rule<World> {
Started,
Scenario(Arc<gherkin::Scenario>, RetryableScenario<World>),
Finished,
}
impl<World> Clone for Rule<World> {
fn clone(&self) -> Self {
match self {
Self::Started => Self::Started,
Self::Scenario(s, ev) => Self::Scenario(Arc::clone(s), ev.clone()),
Self::Finished => Self::Finished,
}
}
}
#[derive(Debug)]
pub enum Step<World> {
Started,
Skipped,
Passed(regex::CaptureLocations, Option<step::Location>),
Failed(
Option<regex::CaptureLocations>,
Option<step::Location>,
Option<Arc<World>>,
StepError,
),
}
impl<World> Clone for Step<World> {
fn clone(&self) -> Self {
match self {
Self::Started => Self::Started,
Self::Skipped => Self::Skipped,
Self::Passed(captures, loc) => Self::Passed(captures.clone(), *loc),
Self::Failed(captures, loc, w, info) => {
Self::Failed(captures.clone(), *loc, w.clone(), info.clone())
}
}
}
}
#[derive(Clone, Debug, Display, Error, From)]
pub enum StepError {
#[display(fmt = "Step doesn't match any function")]
NotFound,
#[display(fmt = "Step match is ambiguous: {}", _0)]
AmbiguousMatch(step::AmbiguousMatchError),
#[display(fmt = "Step panicked. Captured output: {}", "coerce_error(_0)")]
Panic(#[error(not(source))] Info),
}
#[derive(Clone, Copy, Debug)]
pub enum HookType {
Before,
After,
}
#[allow(clippy::use_debug)] impl fmt::Display for HookType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
#[derive(Debug)]
pub enum Hook<World> {
Started,
Passed,
Failed(Option<Arc<World>>, Info),
}
impl<World> Clone for Hook<World> {
fn clone(&self) -> Self {
match self {
Self::Started => Self::Started,
Self::Passed => Self::Passed,
Self::Failed(w, i) => Self::Failed(w.clone(), Arc::clone(i)),
}
}
}
#[derive(Debug)]
pub enum Scenario<World> {
Started,
Hook(HookType, Hook<World>),
Background(Arc<gherkin::Step>, Step<World>),
Step(Arc<gherkin::Step>, Step<World>),
Finished,
}
impl<World> Clone for Scenario<World> {
fn clone(&self) -> Self {
match self {
Self::Started => Self::Started,
Self::Hook(ty, ev) => Self::Hook(*ty, ev.clone()),
Self::Background(bg, ev) => {
Self::Background(Arc::clone(bg), ev.clone())
}
Self::Step(st, ev) => Self::Step(Arc::clone(st), ev.clone()),
Self::Finished => Self::Finished,
}
}
}
impl<World> Scenario<World> {
#[must_use]
pub const fn hook_started(which: HookType) -> Self {
Self::Hook(which, Hook::Started)
}
#[must_use]
pub const fn hook_passed(which: HookType) -> Self {
Self::Hook(which, Hook::Passed)
}
#[must_use]
pub fn hook_failed(
which: HookType,
world: Option<Arc<World>>,
info: Info,
) -> Self {
Self::Hook(which, Hook::Failed(world, info))
}
#[must_use]
pub fn step_started(step: Arc<gherkin::Step>) -> Self {
Self::Step(step, Step::Started)
}
#[must_use]
pub fn background_step_started(step: Arc<gherkin::Step>) -> Self {
Self::Background(step, Step::Started)
}
#[must_use]
pub fn step_passed(
step: Arc<gherkin::Step>,
captures: regex::CaptureLocations,
loc: Option<step::Location>,
) -> Self {
Self::Step(step, Step::Passed(captures, loc))
}
#[must_use]
pub fn background_step_passed(
step: Arc<gherkin::Step>,
captures: regex::CaptureLocations,
loc: Option<step::Location>,
) -> Self {
Self::Background(step, Step::Passed(captures, loc))
}
#[must_use]
pub fn step_skipped(step: Arc<gherkin::Step>) -> Self {
Self::Step(step, Step::Skipped)
}
#[must_use]
pub fn background_step_skipped(step: Arc<gherkin::Step>) -> Self {
Self::Background(step, Step::Skipped)
}
#[must_use]
pub fn step_failed(
step: Arc<gherkin::Step>,
captures: Option<regex::CaptureLocations>,
loc: Option<step::Location>,
world: Option<Arc<World>>,
info: impl Into<StepError>,
) -> Self {
Self::Step(step, Step::Failed(captures, loc, world, info.into()))
}
#[must_use]
pub fn background_step_failed(
step: Arc<gherkin::Step>,
captures: Option<regex::CaptureLocations>,
loc: Option<step::Location>,
world: Option<Arc<World>>,
info: impl Into<StepError>,
) -> Self {
Self::Background(step, Step::Failed(captures, loc, world, info.into()))
}
#[must_use]
pub const fn with_retries(
self,
retries: Option<Retries>,
) -> RetryableScenario<World> {
RetryableScenario {
event: self,
retries,
}
}
}
#[derive(Debug)]
pub struct RetryableScenario<World> {
pub event: Scenario<World>,
pub retries: Option<Retries>,
}
impl<World> Clone for RetryableScenario<World> {
fn clone(&self) -> Self {
Self {
event: self.event.clone(),
retries: self.retries,
}
}
}
#[allow(variant_size_differences)]
#[derive(Clone, Debug)]
pub enum ScenarioFinished {
BeforeHookFailed(Info),
StepPassed,
StepSkipped,
StepFailed(
Option<regex::CaptureLocations>,
Option<step::Location>,
StepError,
),
}