Skip to main content

rstest_bdd_patterns/
errors.rs

1//! Error types shared by the pattern parsing modules.
2
3use std::fmt;
4use thiserror::Error;
5
6/// Additional context for placeholder-related parsing errors.
7///
8/// # Examples
9/// ```
10/// use rstest_bdd_patterns::PlaceholderErrorInfo;
11/// let info = PlaceholderErrorInfo::new("invalid placeholder", 3, Some("value".into()));
12/// assert_eq!(info.placeholder.as_deref(), Some("value"));
13/// assert_eq!(info.position, 3);
14/// ```
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct PlaceholderErrorInfo {
17    /// Human-readable explanation describing why the placeholder failed.
18    pub message: &'static str,
19    /// Zero-based byte offset within the original pattern where the failure occurred.
20    pub position: usize,
21    /// Optional placeholder identifier extracted from the pattern when available.
22    pub placeholder: Option<String>,
23}
24
25impl PlaceholderErrorInfo {
26    /// Create a new error description for a placeholder failure.
27    ///
28    /// # Examples
29    /// ```
30    /// use rstest_bdd_patterns::PlaceholderErrorInfo;
31    /// let info = PlaceholderErrorInfo::new("invalid", 1, None);
32    /// assert_eq!(info.message, "invalid");
33    /// ```
34    #[must_use]
35    pub fn new(message: &'static str, position: usize, placeholder: Option<String>) -> Self {
36        Self {
37            message,
38            position,
39            placeholder,
40        }
41    }
42}
43
44impl fmt::Display for PlaceholderErrorInfo {
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        match &self.placeholder {
47            Some(name) => write!(
48                f,
49                "{} for placeholder `{}` at byte {} (zero-based)",
50                self.message, name, self.position
51            ),
52            None => write!(f, "{} at byte {} (zero-based)", self.message, self.position),
53        }
54    }
55}
56
57/// Errors surfaced while converting step patterns into regular expressions.
58///
59/// # Examples
60/// ```
61/// use rstest_bdd_patterns::{PatternError, PlaceholderErrorInfo};
62/// let info = PlaceholderErrorInfo::new("invalid", 2, Some("count".into()));
63/// let err = PatternError::Placeholder(info.clone());
64/// assert_eq!(err.to_string(), info.to_string());
65/// ```
66#[derive(Debug, Error)]
67pub enum PatternError {
68    #[error("{0}")]
69    /// Error raised when a placeholder token cannot be converted into a matcher.
70    Placeholder(PlaceholderErrorInfo),
71    #[error(transparent)]
72    /// Wrapper used when the generated regular expression fails to compile.
73    Regex(#[from] regex::Error),
74}
75
76pub(crate) fn placeholder_error(
77    message: &'static str,
78    position: usize,
79    placeholder: Option<String>,
80) -> PatternError {
81    PatternError::Placeholder(PlaceholderErrorInfo::new(message, position, placeholder))
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn formats_placeholder_with_name() {
90        let info = PlaceholderErrorInfo::new("invalid", 4, Some("count".into()));
91        assert_eq!(
92            info.to_string(),
93            "invalid for placeholder `count` at byte 4 (zero-based)"
94        );
95    }
96
97    #[test]
98    fn formats_placeholder_without_name() {
99        let info = PlaceholderErrorInfo::new("oops", 1, None);
100        assert_eq!(info.to_string(), "oops at byte 1 (zero-based)");
101    }
102
103    #[test]
104    fn forwards_regex_error_display() {
105        let err = PatternError::Regex(regex::Error::Syntax("bad".into()));
106        assert_eq!(
107            err.to_string(),
108            regex::Error::Syntax("bad".into()).to_string()
109        );
110    }
111}