Skip to main content

colla/
error.rs

1//! Structured errors returned by Colla's public APIs.
2
3use crate::path::Path;
4use crate::value::ValueType;
5use thiserror::Error;
6
7/// Errors produced while constructing canonical Values and typed Changes.
8#[derive(Debug, Clone, PartialEq, Eq, Error)]
9#[non_exhaustive]
10pub enum ValueError {
11    /// A Float or floating-point attribute was NaN or infinite.
12    #[error("float must be finite")]
13    NonFiniteFloat,
14    /// A Map, Attrs, AttrPatch, or MapChange input repeated a key.
15    #[error("duplicate key: {0}")]
16    DuplicateKey(String),
17    /// A logical length or allocation capacity exceeded the platform limit.
18    #[error("length exceeds the platform limit")]
19    LengthOverflow,
20}
21
22/// Errors converting between Unicode scalar and UTF-16 Snapshot positions.
23#[derive(Debug, Clone, PartialEq, Eq, Error)]
24#[non_exhaustive]
25pub enum Utf16PositionError {
26    /// A Unicode scalar position exceeded the sequence length.
27    #[error("code point position {position} is out of bounds (len {len})")]
28    CodePointOutOfBounds {
29        /// Requested Unicode scalar/embed position.
30        position: usize,
31        /// Logical sequence length.
32        len: usize,
33    },
34    /// A UTF-16 position exceeded the sequence's UTF-16 length.
35    #[error("UTF-16 position {position} is out of bounds (len {len})")]
36    Utf16OutOfBounds {
37        /// Requested UTF-16 position.
38        position: usize,
39        /// UTF-16 sequence length.
40        len: usize,
41    },
42    /// A UTF-16 position fell between a surrogate pair's code units.
43    #[error("UTF-16 position {position} is inside a surrogate pair")]
44    InvalidUtf16Boundary {
45        /// Requested UTF-16 position.
46        position: usize,
47    },
48}
49
50/// Errors applying a Change to a concrete Snapshot.
51#[derive(Debug, Clone, PartialEq, Eq, Error)]
52#[non_exhaustive]
53pub enum ApplyError {
54    /// The Change kind did not match the target Value type.
55    #[error("type mismatch at {path}: expected {expected:?}, got {actual:?}")]
56    TypeMismatch {
57        /// Snapshot-relative location of the mismatch.
58        path: Path,
59        /// Value type required by the Change.
60        expected: ValueType,
61        /// Actual Snapshot Value type.
62        actual: ValueType,
63    },
64    /// A Map operation required a key that was absent.
65    #[error("missing map key at {path}: {key}")]
66    MissingKey {
67        /// Path to the containing Map.
68        path: Path,
69        /// Required key.
70        key: String,
71    },
72    /// A Map insert targeted a key that already existed.
73    #[error("map key already exists at {path}: {key}")]
74    ExistingKey {
75        /// Path to the containing Map.
76        path: Path,
77        /// Existing key.
78        key: String,
79    },
80    /// A List operation addressed an element outside the Snapshot.
81    #[error("list index {index} out of bounds at {path} (len {len})")]
82    IndexOutOfBounds {
83        /// Path to the containing List.
84        path: Path,
85        /// Requested element index.
86        index: usize,
87        /// Snapshot List length.
88        len: usize,
89    },
90    /// A sequence Change consumed beyond the available base sequence.
91    #[error("sequence operation consumes beyond the input at {path}")]
92    SequenceOutOfBounds {
93        /// Path to the sequence.
94        path: Path,
95    },
96    /// Checked Int addition overflowed `i64`.
97    #[error("integer addition overflow at {path}")]
98    IntegerOverflow {
99        /// Path to the Int Value.
100        path: Path,
101    },
102    /// The resulting sequence length or capacity exceeded the platform limit.
103    #[error("sequence logical length overflow at {path}")]
104    SequenceLengthOverflow {
105        /// Path to the sequence.
106        path: Path,
107    },
108}
109
110/// Errors composing sequential Changes.
111#[derive(Debug, Clone, PartialEq, Eq, Error)]
112#[non_exhaustive]
113pub enum ComposeError {
114    /// The root Change kinds cannot be composed sequentially.
115    #[error("incompatible sequential changes: {left} then {right}")]
116    IncompatibleKinds {
117        /// First Change kind.
118        left: &'static str,
119        /// Second Change kind.
120        right: &'static str,
121    },
122    /// Two operations on one Map key cannot be composed.
123    #[error("sequential map operations are incompatible for key {0}")]
124    IncompatibleMapEntry(String),
125    /// Applying a Change to an intermediate replacement failed.
126    #[error(transparent)]
127    Apply(#[from] ApplyError),
128    /// The canonical composed Change exceeded a platform length limit.
129    #[error("composed change length exceeds the platform limit")]
130    LengthOverflow,
131}
132
133/// Errors transforming two concurrent Changes.
134#[derive(Debug, Clone, PartialEq, Eq, Error)]
135#[non_exhaustive]
136pub enum TransformError {
137    /// The Changes cannot describe concurrent operations on one Value type.
138    #[error("changes cannot share one base value: {left} vs {right}")]
139    IncompatibleKinds {
140        /// Left Change kind.
141        left: &'static str,
142        /// Right Change kind.
143        right: &'static str,
144    },
145    /// Map entry operations cannot share one valid base-key state.
146    #[error("map entry changes cannot share one base key: {0}")]
147    IncompatibleMapEntry(String),
148    /// A transformed Change exceeded a platform length limit.
149    #[error("transformed change length exceeds the platform limit")]
150    LengthOverflow,
151}
152
153/// Errors constructing an inverse Change.
154#[derive(Debug, Clone, PartialEq, Eq, Error)]
155#[non_exhaustive]
156pub enum InvertError {
157    /// The Change was not applicable to the supplied original Snapshot.
158    #[error(transparent)]
159    Apply(#[from] ApplyError),
160    /// The inverse Change exceeded a platform length limit.
161    #[error("inverse change length exceeds the platform limit")]
162    LengthOverflow,
163}
164
165/// Errors decoding canonical Value and Change binary bodies.
166#[derive(Debug, Clone, PartialEq, Eq, Error)]
167#[non_exhaustive]
168pub enum CodecError {
169    /// Input ended before the current value was complete.
170    #[error("unexpected end of input at byte {offset}")]
171    UnexpectedEof {
172        /// Byte offset where more input was required.
173        offset: usize,
174    },
175    /// A tag was not defined for the current codec context.
176    #[error("unknown tag 0x{tag:02x} for {context} at byte {offset}")]
177    UnknownTag {
178        /// Byte offset of the tag.
179        offset: usize,
180        /// Unknown tag byte.
181        tag: u8,
182        /// Value, Change, operation, or attribute context.
183        context: &'static str,
184    },
185    /// A varint used more bytes than its canonical shortest encoding.
186    #[error("non-minimal varint at byte {offset}")]
187    NonMinimalVarint {
188        /// Byte offset of the varint.
189        offset: usize,
190    },
191    /// A decoded integer could not fit the required target type.
192    #[error("integer is out of range at byte {offset}")]
193    IntegerOutOfRange {
194        /// Byte offset of the integer.
195        offset: usize,
196    },
197    /// String bytes were not valid UTF-8.
198    #[error("invalid UTF-8 at byte {offset}")]
199    InvalidUtf8 {
200        /// Byte offset of the invalid string.
201        offset: usize,
202    },
203    /// The input represented a structurally valid but non-canonical form.
204    #[error("non-canonical {context} at byte {offset}: {reason}")]
205    NonCanonical {
206        /// Byte offset where non-canonical input was detected.
207        offset: usize,
208        /// Value, Change, operation, or attribute context.
209        context: &'static str,
210        /// Stable diagnostic reason within the Rust API.
211        reason: &'static str,
212    },
213    /// Bytes remained after one complete Value or Change.
214    #[error("trailing bytes beginning at byte {offset}")]
215    TrailingBytes {
216        /// Byte offset of the first trailing byte.
217        offset: usize,
218    },
219    /// Untrusted input exceeded one receiver-defined resource limit.
220    #[error("resource limit exceeded: {name} ({actual} > {limit})")]
221    LimitExceeded {
222        /// Resource category.
223        name: &'static str,
224        /// Observed resource usage.
225        actual: usize,
226        /// Configured maximum.
227        limit: usize,
228    },
229    /// Decoded content violated a canonical Value construction rule.
230    #[error(transparent)]
231    Value(#[from] ValueError),
232}