Skip to main content

tfparser_core/
error.rs

1//! Top-level error type for `tfparser-core`.
2//!
3//! Per CLAUDE.md § Error Handling: every fallible API in this crate returns
4//! [`Result<T>`] aliased to `std::result::Result<T, Error>`. Phase-specific
5//! errors (`DiscoveryError`, `LoaderError`, `EvalError`, …) plug in via
6//! `#[from]` once their owning modules land. Until then the variants below
7//! are sufficient — none of them are reachable in Phase 1 since no real
8//! pipeline runs yet, but they pin the public error contract so downstream
9//! crates can match on the shape today.
10
11use std::path::PathBuf;
12
13use thiserror::Error;
14
15/// Crate-wide result alias.
16pub type Result<T> = std::result::Result<T, Error>;
17
18/// Top-level error returned by every public `tfparser_core` API.
19///
20/// Per the spec's stability contract ([10-data-model.md § Versioning], the
21/// variants below are `#[non_exhaustive]` so future phases can add error
22/// kinds without a major bump. Match arms should always include a
23/// catch-all.
24#[derive(Debug, Error)]
25#[non_exhaustive]
26pub enum Error {
27    /// A validated newtype rejected its input (length, charset, or shape
28    /// invariant). The accompanying [`ValidationError`] names the field and
29    /// the rule.
30    #[error("validation failed: {0}")]
31    Validation(#[from] ValidationError),
32
33    /// I/O error reading or writing a path.
34    #[error("i/o error at {path}: {source}")]
35    Io {
36        /// Path that triggered the error.
37        path: PathBuf,
38        /// Underlying I/O error.
39        #[source]
40        source: std::io::Error,
41    },
42
43    /// A configured resource limit was exceeded. The accompanying
44    /// [`crate::diagnostic::LimitKind`] identifies which one.
45    #[error("limit exceeded ({kind:?}): observed {observed} > limit {limit}")]
46    Limit {
47        /// Which limit category fired.
48        kind: crate::diagnostic::LimitKind,
49        /// Observed value.
50        observed: u64,
51        /// Configured limit.
52        limit: u64,
53    },
54
55    /// Provider resolver (Phase 7) raised a fatal error.
56    #[error(transparent)]
57    Provider(#[from] crate::provider::ProviderError),
58
59    /// Parquet exporter (Phase 3+) raised a fatal error.
60    #[error(transparent)]
61    Export(#[from] crate::exporter::ExportError),
62}
63
64/// Reasons a validated newtype constructor (e.g. [`crate::Address::new`])
65/// can reject its input.
66///
67/// Field names are stable; new variants are additive.
68#[derive(Debug, Error)]
69#[non_exhaustive]
70pub enum ValidationError {
71    /// The candidate string was empty when a non-empty value was required.
72    #[error("`{field}` must not be empty")]
73    Empty {
74        /// Name of the field being validated.
75        field: &'static str,
76    },
77
78    /// A required builder field was not set before [`crate::Parser::builder`]
79    /// finalisation. The string is the field name (e.g. `"workspace_root"`).
80    #[error("required field `{0}` not set")]
81    MissingField(&'static str),
82
83    /// The candidate exceeds the per-field byte cap.
84    #[error("`{field}` exceeds maximum byte length ({observed} > {limit})")]
85    TooLong {
86        /// Name of the field being validated.
87        field: &'static str,
88        /// Observed byte length.
89        observed: usize,
90        /// Configured maximum.
91        limit: usize,
92    },
93
94    /// A disallowed character appeared in the candidate.
95    #[error(
96        "`{field}` contains disallowed character {:?} at byte {offset}",
97        char::from(*byte)
98    )]
99    BadChar {
100        /// Name of the field being validated.
101        field: &'static str,
102        /// Offending byte (rendered as a `char` in the message).
103        byte: u8,
104        /// Byte offset of the offending character.
105        offset: usize,
106    },
107
108    /// The candidate failed a higher-level structural rule (balanced
109    /// quotes/brackets, expected digit count, etc.). `rule` is a short
110    /// machine-readable token (e.g. `"unbalanced-brackets"`); the message
111    /// embeds the input snippet for diagnostics.
112    #[error("`{field}` failed rule `{rule}`")]
113    Shape {
114        /// Name of the field being validated.
115        field: &'static str,
116        /// Rule identifier — short machine-readable token.
117        rule: &'static str,
118    },
119
120    /// The candidate falls outside an allowed numeric range. Use for
121    /// integer-shaped invariants — codec levels, percentages, port numbers.
122    #[error("`{field}` out of range [{min}, {max}]: got {got}")]
123    Range {
124        /// Name of the field being validated.
125        field: &'static str,
126        /// Inclusive minimum.
127        min: i64,
128        /// Inclusive maximum.
129        max: i64,
130        /// Observed value.
131        got: i64,
132    },
133}