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}