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}