Skip to main content

forbidden_regex/
error.rs

1//! What:    Compile-time error type for the forbidden-regex engine.
2//! Why:     This file is the Rust module that groups the error implementation, so the
3//!          compiler gives those items one namespace and sibling modules can import that name.
4//!
5//! In TS you'd write (pseudocode):
6//! ```ts
7//! // module error: see exported functions and types below.
8//! ```
9
10/// What:    Imports the formatter pieces used to render a human-readable error message.
11/// Why:     The code below uses `fmt` directly; importing from `std` keeps each call site
12///          focused on the matcher logic instead of the full Rust path.
13///
14/// In TS you'd write (pseudocode):
15/// ```ts
16/// import { fmt } from "std";
17/// ```
18use std::fmt;
19
20/// Reasons a pattern (or a serialized DFA) is rejected before it can match.
21///
22/// What: every failure path in parsing, the empty-match guard, the DFA state
23/// cap, and (de)serialization funnels into one of these variants.
24/// Why: callers (the scanner, tests) get a single typed error to match on, and
25/// `compile`/`new` never panic on bad input; they return one of these instead.
26///
27/// In TS you'd write (pseudocode):
28/// ```ts
29/// type CompileError =
30///   | { kind: "variant" };
31/// ```
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum CompileError {
34    /// A syntax or unsupported-construct rejection at a byte offset.
35    ///
36    /// What: holds the offset into the pattern and a message naming what was
37    /// wrong (unsupported operator, bad escape, unbalanced bracket, operand not
38    /// a single atom, mixed operators, stacked quantifier, bad repetition).
39    /// Why: one variant covers every parse rejection so the surface stays small
40    /// while still pointing the rule author at the exact spot.
41    ///
42    /// In TS you'd write (pseudocode):
43    /// ```ts
44    /// // Same step as the Rust statement below, written with ordinary TS objects/functions.
45    /// ```
46    Syntax {
47        /// What:    Byte offset into the pattern where the problem was detected.
48        /// Why:     `pos` stores byte offset into the pattern where the problem was detected, so
49        ///          matcher code reads that precomputed state by name instead of recomputing or
50        ///          passing it separately.
51        ///
52        /// In TS you'd write (pseudocode):
53        /// ```ts
54        /// pos: number;
55        /// ```
56        pos: usize,
57        /// What:    Human-readable description of what was rejected.
58        /// Why:     `message` stores human-readable description of what was rejected, so matcher
59        ///          code reads that precomputed state by name instead of recomputing or passing
60        ///          it separately.
61        ///
62        /// In TS you'd write (pseudocode):
63        /// ```ts
64        /// message: string;
65        /// ```
66        message: String,
67    },
68    /// The pattern can match the empty string, so under unanchored search it
69    /// would match every input.
70    ///
71    /// What: raised when the parsed root is nullable in some realizable anchor
72    /// context. Why: such a rule is a footgun (flags every line); rejecting it
73    /// at compile time is safer than silently matching everything.
74    ///
75    /// In TS you'd write (pseudocode):
76    /// ```ts
77    /// // Same step as the Rust statement below, written with ordinary TS objects/functions.
78    /// ```
79    EmptyMatchable,
80    /// Determinization produced more states than the configured cap.
81    ///
82    /// What: complement and intersection can blow up the state count on
83    /// pathological patterns. Why: a hard cap turns that into a clean error
84    /// instead of unbounded memory use.
85    ///
86    /// In TS you'd write (pseudocode):
87    /// ```ts
88    /// // Same step as the Rust statement below, written with ordinary TS objects/functions.
89    /// ```
90    StateCap {
91        /// What:    State-count limit that was exceeded.
92        /// Why:     `limit` stores state-count limit that was exceeded, so matcher code reads
93        ///          that precomputed state by name instead of recomputing or passing it
94        ///          separately.
95        ///
96        /// In TS you'd write (pseudocode):
97        /// ```ts
98        /// limit: number;
99        /// ```
100        limit: usize,
101    },
102    /// Encoding a compiled automaton to bytes failed.
103    ///
104    /// What: wraps a bincode serialization failure as a string. Why: keeps the
105    /// public error free of a bincode type in its signature.
106    ///
107    /// In TS you'd write (pseudocode):
108    /// ```ts
109    /// // Same step as the Rust statement below, written with ordinary TS objects/functions.
110    /// ```
111    Serialize {
112        /// What:    Underlying codec failure rendered as text.
113        /// Why:     `message` stores underlying codec failure rendered as text, so matcher code
114        ///          reads that precomputed state by name instead of recomputing or passing it
115        ///          separately.
116        ///
117        /// In TS you'd write (pseudocode):
118        /// ```ts
119        /// message: string;
120        /// ```
121        message: String,
122    },
123    /// A deserialized automaton failed structural validation.
124    ///
125    /// What: raised by `from_bytes` when decoded indices are out of bounds or
126    /// lengths are inconsistent. Why: a hostile or corrupt blob must be rejected
127    /// before it is ever executed, so the match loop can never read out of
128    /// bounds.
129    ///
130    /// In TS you'd write (pseudocode):
131    /// ```ts
132    /// // Same step as the Rust statement below, written with ordinary TS objects/functions.
133    /// ```
134    Invalid {
135        /// What:    Description of which invariant the decoded automaton violated.
136        /// Why:     `message` stores description of which invariant the decoded automaton
137        ///          violated, so matcher code reads that precomputed state by name instead of
138        ///          recomputing or passing it separately.
139        ///
140        /// In TS you'd write (pseudocode):
141        /// ```ts
142        /// message: string;
143        /// ```
144        message: String,
145    },
146}
147
148/// What:    Renders a `CompileError` for end users and logs.
149/// Why:     The program attaches these functions to the named Rust type so callers can use
150///          method syntax.
151///
152/// In TS you'd write (pseudocode):
153/// ```ts
154/// // Methods are written inside a class or as functions that take the value.
155/// ```
156impl fmt::Display for CompileError {
157    /// Writes a one-line description of the error.
158    ///
159    /// What: matches each variant to a sentence. Why: `Display` is what the
160    /// scanner surfaces and what `Error` builds on.
161    ///
162    /// In TS you'd write (pseudocode):
163    /// ```ts
164    /// function fmt(f: fmt.Formatter<'_>): fmt.Result {
165    ///   // Rust body below is the implementation.
166    /// }
167    /// ```
168    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169        // What: branch per variant; no `match` value is discarded.
170        // Why: each variant carries different fields to interpolate.
171        //
172        // In TS you'd write (pseudocode):
173        // ```ts
174        // // Same step as the Rust statement below, written with ordinary TS objects/functions.
175        // ```
176        match self {
177            CompileError::Syntax { pos, message } => {
178                return write!(f, "syntax error at byte {pos}: {message}")
179            }
180            CompileError::EmptyMatchable => {
181                return write!(f, "pattern can match the empty string, which would match every input")
182            }
183            CompileError::StateCap { limit } => {
184                return write!(f, "pattern exceeded the DFA state cap of {limit}")
185            }
186            CompileError::Serialize { message } => {
187                return write!(f, "failed to serialize automaton: {message}")
188            }
189            CompileError::Invalid { message } => {
190                return write!(f, "invalid serialized automaton: {message}")
191            }
192        }
193    }
194}
195
196/// What:    Lets `CompileError` participate in the standard error ecosystem.
197/// Why:     The program attaches these functions to the named Rust type so callers can use
198///          method syntax.
199///
200/// In TS you'd write (pseudocode):
201/// ```ts
202/// // Methods are written inside a class or as functions that take the value.
203/// ```
204impl std::error::Error for CompileError {}
205
206/// What:    Unit tests for error rendering, in a sidecar (max-lines exempt).
207/// Why:     The package keeps that concept in a separate Rust file so this module can refer to
208///          it by name.
209///
210/// In TS you'd write (pseudocode):
211/// ```ts
212/// import "./tests";
213/// ```
214#[cfg(test)]
215#[path = "error_tests.rs"]
216mod tests;