Skip to main content

active_infinity/
error.rs

1//! Stable, machine-readable error surface.
2//!
3//! Every failure mode of the codec, validator, view and stream layers maps
4//! to exactly one variant here, and every variant carries a permanent
5//! `code()` string. Agents branch on codes, never on messages: the code
6//! namespace is part of the wire contract (`docs/spec/layout.md` §9) and
7//! codes are never reused or renumbered.
8//!
9//! Failure is always *closed*: a buffer that cannot be fully validated is
10//! rejected before any caller sees a node, a string, a blob or a view.
11
12use std::fmt;
13
14/// Result alias for every fallible operation in this crate.
15pub type Result<T> = std::result::Result<T, Error>;
16
17/// A codec, validation or streaming failure.
18///
19/// Variant documentation states the condition; field names are stable API.
20#[allow(missing_docs)]
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub enum Error {
23    // ---- framing and header -------------------------------------------
24    /// Input does not start with the `AINF` magic.
25    Magic,
26    /// Unsupported major format version.
27    VersionMajor(u16),
28    /// Unsupported minor format version (readers never guess forward).
29    VersionMinor(u16),
30    /// Header is shorter than the fixed 160-byte prefix.
31    HeaderTruncated,
32    /// Header `header_size` field is not the supported fixed size.
33    HeaderSize(u8),
34    /// Header flags set bits this reader does not understand.
35    UnknownFlags(u16),
36    /// A reserved header or record field was non-zero.
37    ReservedNonZero { section: &'static str, offset: u64 },
38    /// A section offset or byte range is out of bounds, misaligned,
39    /// overlapping, or not in canonical ascending order.
40    Offset { section: &'static str, offset: u64 },
41    /// The buffer is longer or shorter than the sections it declares.
42    Truncated { needed: u64, found: u64 },
43    /// The buffer declares trailing bytes after the final section.
44    TrailingBytes { at: u64 },
45    /// A count field cannot be satisfied by the remaining bytes, or
46    /// exceeds the configured limits.
47    Count { field: &'static str, value: u64 },
48    /// Non-zero padding byte (canonical images pad with zero only).
49    Padding { offset: u64 },
50    /// Stored content hash does not match the recomputed hash.
51    HashMismatch,
52    /// The declared dialect hash is not the dialect this reader implements.
53    DialectMismatch(u64),
54    /// A buffer self-declares canonical form but re-encoding differs.
55    NotCanonical,
56
57    // ---- structure -----------------------------------------------------
58    /// A string id is out of range.
59    StringRef(u32),
60    /// A node id is out of range.
61    NodeRef(u32),
62    /// A node record violates the per-kind contract.
63    Node { id: u32, reason: &'static str },
64    /// String heap bytes are not valid UTF-8.
65    Utf8 { offset: u64 },
66    /// A unit discriminant is not one this reader implements.
67    UnknownUnit(u32),
68    /// A quantity or type reference is out of range.
69    UnitRef(u32),
70    /// A type-table entry is malformed.
71    Type { id: u32, reason: &'static str },
72    /// An edge endpoint is out of range or an edge is self-referential.
73    Edge { from: u32, to: u32 },
74    /// An annotation record is malformed.
75    Annotation { id: u32, reason: &'static str },
76    /// The provenance block is malformed.
77    Provenance(&'static str),
78    /// An extension block is malformed or overlaps another block.
79    Extension { tag: u64, reason: &'static str },
80    /// A blob entry is malformed.
81    Blob { id: u32, reason: &'static str },
82    /// A secret-table entry is malformed.
83    SecretEntry(u32),
84    /// A table field name repeats.
85    DuplicateField { node: u32 },
86    /// A binding name repeats.
87    DuplicateBinding,
88    /// A node is on a dependency cycle, or depends on one.
89    Cycle { node: u32 },
90    /// Nesting depth exceeds the configured limit while validating.
91    Depth { limit: u32 },
92
93    // ---- limits --------------------------------------------------------
94    /// A configured decode limit was exceeded.
95    Limit { kind: &'static str, limit: u64 },
96    /// A caller-supplied value is outside its legal range.
97    InvalidArgument(&'static str),
98    /// The Active evaluator rejected the module; the payload is its stable
99    /// diagnostic class (`S001`, `T001`, `X00n`, ...).
100    Evaluation(&'static str),
101
102    // ---- encoding ------------------------------------------------------
103    /// A module cannot be encoded (dangling reference, duplicate key, ...).
104    Encode(&'static str),
105    /// A node id referenced during encoding does not exist.
106    EncodeDangling(u32),
107    /// The encoded image would exceed `u32` offset space.
108    TooLarge,
109
110    // ---- streaming -----------------------------------------------------
111    /// Stream frame magic `AINS` missing.
112    StreamMagic,
113    /// Unsupported stream frame version.
114    StreamVersion(u8),
115    /// A stream frame length exceeds the limit or the available bytes.
116    StreamFrameLen { declared: u32, available: u64 },
117    /// A stream frame kind is unknown.
118    StreamFrameKind(u8),
119    /// A stream frame is out of sequence or the stream is truncated.
120    StreamSequence { expected: u64, found: u64 },
121    /// A frame arrived before `Begin` or after `End`.
122    StreamState(&'static str),
123    /// The reassembled module hash does not match the `End` frame.
124    StreamHashMismatch,
125    /// A delta operation cannot be applied to the current module state.
126    StreamDelta(&'static str),
127    /// The stream ended without an `End` frame.
128    StreamUnterminated,
129}
130
131impl Error {
132    /// Permanent, machine-readable code. Stable across releases.
133    pub const fn code(&self) -> &'static str {
134        match self {
135            Error::Magic => "infinity/magic",
136            Error::VersionMajor(_) => "infinity/version-major",
137            Error::VersionMinor(_) => "infinity/version-minor",
138            Error::HeaderTruncated => "infinity/header-truncated",
139            Error::HeaderSize(_) => "infinity/header-size",
140            Error::UnknownFlags(_) => "infinity/flags",
141            Error::ReservedNonZero { .. } => "infinity/reserved",
142            Error::Offset { .. } => "infinity/offset",
143            Error::Truncated { .. } => "infinity/truncated",
144            Error::TrailingBytes { .. } => "infinity/trailing-bytes",
145            Error::Count { .. } => "infinity/count",
146            Error::Padding { .. } => "infinity/padding",
147            Error::HashMismatch => "infinity/hash-mismatch",
148            Error::DialectMismatch(_) => "infinity/dialect",
149            Error::NotCanonical => "infinity/not-canonical",
150            Error::StringRef(_) => "infinity/string-ref",
151            Error::NodeRef(_) => "infinity/node-ref",
152            Error::Node { .. } => "infinity/node",
153            Error::Utf8 { .. } => "infinity/utf8",
154            Error::UnknownUnit(_) => "infinity/unknown-unit",
155            Error::UnitRef(_) => "infinity/unit-ref",
156            Error::Type { .. } => "infinity/type",
157            Error::Edge { .. } => "infinity/edge",
158            Error::Annotation { .. } => "infinity/annotation",
159            Error::Provenance(_) => "infinity/provenance",
160            Error::Extension { .. } => "infinity/extension",
161            Error::Blob { .. } => "infinity/blob",
162            Error::SecretEntry(_) => "infinity/secret-entry",
163            Error::DuplicateField { .. } => "infinity/duplicate-field",
164            Error::DuplicateBinding => "infinity/duplicate-binding",
165            Error::Cycle { .. } => "infinity/cycle",
166            Error::Depth { .. } => "infinity/depth",
167            Error::Limit { .. } => "infinity/limit",
168            Error::InvalidArgument(_) => "infinity/invalid-argument",
169            Error::Evaluation(_) => "infinity/evaluation",
170            Error::Encode(_) => "infinity/encode",
171            Error::EncodeDangling(_) => "infinity/encode-dangling",
172            Error::TooLarge => "infinity/too-large",
173            Error::StreamMagic => "infinity/stream-magic",
174            Error::StreamVersion(_) => "infinity/stream-version",
175            Error::StreamFrameLen { .. } => "infinity/stream-frame-len",
176            Error::StreamFrameKind(_) => "infinity/stream-frame-kind",
177            Error::StreamSequence { .. } => "infinity/stream-sequence",
178            Error::StreamState(_) => "infinity/stream-state",
179            Error::StreamHashMismatch => "infinity/stream-hash",
180            Error::StreamDelta(_) => "infinity/stream-delta",
181            Error::StreamUnterminated => "infinity/stream-unterminated",
182        }
183    }
184
185    /// True when the failure means the input is untrusted or corrupt.
186    ///
187    /// All decode-side failures are fatal for the buffer: callers must drop
188    /// it rather than retrying with a different configuration.
189    pub const fn is_fatal(&self) -> bool {
190        !matches!(self, Error::InvalidArgument(_))
191    }
192}
193
194impl fmt::Display for Error {
195    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196        // Messages never echo payload bytes: a malformed buffer must not be
197        // able to turn an error report into a secret-leak channel.
198        match self {
199            Error::Magic => write!(f, "not an Active Infinity buffer (bad magic)"),
200            Error::VersionMajor(v) => write!(f, "unsupported major version {v}"),
201            Error::VersionMinor(v) => write!(f, "unsupported minor version {v}"),
202            Error::HeaderTruncated => write!(f, "buffer shorter than the fixed header"),
203            Error::HeaderSize(n) => write!(f, "unsupported header size {n}"),
204            Error::UnknownFlags(bits) => write!(f, "unknown header flag bits {bits:#06x}"),
205            Error::ReservedNonZero { section, offset } => {
206                write!(f, "reserved field in {section} is non-zero at {offset}")
207            }
208            Error::Offset { section, offset } => {
209                write!(f, "invalid {section} offset/range at {offset}")
210            }
211            Error::Truncated { needed, found } => {
212                write!(f, "truncated buffer: need {needed} bytes, found {found}")
213            }
214            Error::TrailingBytes { at } => write!(f, "trailing bytes after {at}"),
215            Error::Count { field, value } => write!(f, "invalid {field} count {value}"),
216            Error::Padding { offset } => write!(f, "non-zero padding at {offset}"),
217            Error::HashMismatch => write!(f, "content hash mismatch"),
218            Error::DialectMismatch(h) => write!(f, "dialect hash {h:#018x} not supported"),
219            Error::NotCanonical => write!(f, "buffer is not in canonical form"),
220            Error::StringRef(id) => write!(f, "dangling string reference {id}"),
221            Error::NodeRef(id) => write!(f, "dangling node reference {id}"),
222            Error::Node { id, reason } => write!(f, "node {id} is invalid: {reason}"),
223            Error::Utf8 { offset } => write!(f, "invalid UTF-8 in string heap at {offset}"),
224            Error::UnknownUnit(d) => write!(f, "unknown unit discriminant {d}"),
225            Error::UnitRef(id) => write!(f, "dangling unit reference {id}"),
226            Error::Type { id, reason } => write!(f, "type {id} is invalid: {reason}"),
227            Error::Edge { from, to } => write!(f, "invalid edge {from} -> {to}"),
228            Error::Annotation { id, reason } => write!(f, "annotation {id} is invalid: {reason}"),
229            Error::Provenance(reason) => write!(f, "provenance block is invalid: {reason}"),
230            Error::Extension { tag, reason } => {
231                write!(f, "extension {tag} is invalid: {reason}")
232            }
233            Error::Blob { id, reason } => write!(f, "blob {id} is invalid: {reason}"),
234            Error::SecretEntry(id) => write!(f, "secret entry {id} is invalid"),
235            Error::DuplicateField { node } => write!(f, "table node {node} repeats a field name"),
236            Error::DuplicateBinding => write!(f, "duplicate binding name"),
237            Error::Cycle { node } => write!(
238                f,
239                "dependency cycle: node {node} is on a cycle or depends on one"
240            ),
241            Error::Depth { limit } => write!(f, "nesting deeper than {limit}"),
242            Error::Limit { kind, limit } => write!(f, "{kind} limit {limit} exceeded"),
243            Error::InvalidArgument(what) => write!(f, "invalid argument: {what}"),
244            Error::Evaluation(code) => write!(f, "Active evaluation failed: {code}"),
245            Error::Encode(what) => write!(f, "cannot encode module: {what}"),
246            Error::EncodeDangling(id) => write!(f, "cannot encode: dangling node {id}"),
247            Error::TooLarge => write!(f, "module exceeds the 4 GiB addressable image"),
248            Error::StreamMagic => write!(f, "not an Active Infinity stream frame"),
249            Error::StreamVersion(v) => write!(f, "unsupported stream frame version {v}"),
250            Error::StreamFrameLen {
251                declared,
252                available,
253            } => {
254                write!(
255                    f,
256                    "stream frame declares {declared} bytes, {available} available"
257                )
258            }
259            Error::StreamFrameKind(k) => write!(f, "unknown stream frame kind {k}"),
260            Error::StreamSequence { expected, found } => {
261                write!(
262                    f,
263                    "stream frame out of order: expected {expected}, got {found}"
264                )
265            }
266            Error::StreamState(what) => write!(f, "stream state error: {what}"),
267            Error::StreamHashMismatch => write!(f, "streamed module hash mismatch"),
268            Error::StreamDelta(what) => write!(f, "delta cannot be applied: {what}"),
269            Error::StreamUnterminated => write!(f, "stream ended without an end frame"),
270        }
271    }
272}
273
274impl std::error::Error for Error {}
275
276impl From<std::str::Utf8Error> for Error {
277    fn from(_: std::str::Utf8Error) -> Self {
278        Error::Utf8 { offset: 0 }
279    }
280}