varar_core/error.rs
1//! The error model: the Rust replacement for Java var-core's typed exception
2//! hierarchy (`CellMismatchException`,
3//! `ReturnShapeException`, `UnexpectedPassException`, author `AssertionError`).
4//! `Result`/panic-catch replace throw; `instanceof` dispatch becomes `match`.
5
6use crate::cell_diff::CellDiff;
7use std::any::Any;
8
9/// A handler-signalled failure (author `Err(...)` or a caught panic) — the
10/// analog of an arbitrary thrown `RuntimeException`/`AssertionError`.
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct HandlerError {
13 pub message: String,
14}
15
16impl HandlerError {
17 pub fn new(message: impl Into<String>) -> HandlerError {
18 HandlerError {
19 message: message.into(),
20 }
21 }
22
23 /// Extracts a message from a `catch_unwind` panic payload (`&str`/`String`,
24 /// else a generic fallback).
25 pub fn from_panic(payload: Box<dyn Any + Send>) -> HandlerError {
26 let message = if let Some(s) = payload.downcast_ref::<&str>() {
27 (*s).to_string()
28 } else if let Some(s) = payload.downcast_ref::<String>() {
29 s.clone()
30 } else {
31 "handler panicked".to_string()
32 };
33 HandlerError { message }
34 }
35}
36
37/// A step failure verdict — the closed union replacing the exception hierarchy.
38#[derive(Clone, Debug, PartialEq)]
39pub enum StepError {
40 /// One or more compared cells differ — an inline capture, a table cell, a
41 /// header-bound row's cell, or a doc string (only the failing cells).
42 CellMismatch(Vec<CellDiff>),
43 /// Wrong return type/shape — an author mistake, not a value diff.
44 ReturnShape(String),
45 /// An `error`-fenced example ran without failing.
46 UnexpectedPass,
47 /// An author-signalled failure (`Err`) or a caught panic.
48 Handler(HandlerError),
49}
50
51impl StepError {
52 /// The human-readable message (`getMessage()` parity).
53 pub fn message(&self) -> String {
54 match self {
55 StepError::CellMismatch(cells) => cells
56 .iter()
57 .map(|c| format!("{}: expected {} but was {}", c.column, c.expected, c.actual))
58 .collect::<Vec<_>>()
59 .join("; "),
60 StepError::ReturnShape(msg) => msg.clone(),
61 StepError::UnexpectedPass => "expected the example to fail, but it passed".to_string(),
62 StepError::Handler(e) => e.message.clone(),
63 }
64 }
65
66 /// The failing cells of a [`StepError::CellMismatch`], else `None`
67 /// (`isCellMismatchException` parity).
68 pub fn as_cell_mismatch(&self) -> Option<&[CellDiff]> {
69 match self {
70 StepError::CellMismatch(cells) => Some(cells),
71 _ => None,
72 }
73 }
74}
75
76/// Where a failure points in the `.md` — the structural replacement for Java's
77/// synthetic `StackTraceElement` injection.
78#[derive(Clone, Debug, PartialEq, Eq)]
79pub struct FailureLocation {
80 pub label: String,
81 pub path: String,
82 pub line: usize,
83}
84
85/// A caught step failure plus its (optional) source location.
86#[derive(Clone, Debug, PartialEq)]
87pub struct StepFailure {
88 pub error: StepError,
89 pub location: Option<FailureLocation>,
90}
91
92impl StepFailure {
93 /// A failure with no attached location (fallback-line path).
94 pub fn bare(error: StepError) -> StepFailure {
95 StepFailure {
96 error,
97 location: None,
98 }
99 }
100}
101
102/// Mirrors `JSON.stringify`'s quoting of the TS doc-string error message closely
103/// enough for a human-readable message (never parsed back).
104/// Renders `s` the way `JSON.stringify` does in the TypeScript port.
105///
106/// Every port quotes doc-string mismatch messages identically because the text is
107/// matched by substring in an `error` fence — a port that quotes differently fails
108/// a spec its siblings pass. Escaping only `\\`, `"` and `\n` is not enough: doc
109/// strings routinely carry tab-indented code.
110pub(crate) fn quote(s: &str) -> String {
111 let mut out = String::with_capacity(s.len() + 2);
112 out.push('"');
113 for c in s.chars() {
114 match c {
115 '\\' => out.push_str("\\\\"),
116 '"' => out.push_str("\\\""),
117 '\n' => out.push_str("\\n"),
118 '\r' => out.push_str("\\r"),
119 '\t' => out.push_str("\\t"),
120 '\u{08}' => out.push_str("\\b"),
121 '\u{0c}' => out.push_str("\\f"),
122 c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
123 c => out.push(c),
124 }
125 }
126 out.push('"');
127 out
128}
129
130/// A registration-time (author-wiring) error — never a step failure.
131#[derive(Clone, Debug, PartialEq, Eq)]
132pub enum RegistryError {
133 /// A duplicate step expression; the message lists both source positions.
134 DuplicateStep(String),
135 /// The cucumber expression failed to compile (e.g. undefined parameter type).
136 Expression(String),
137}