Skip to main content

atproto_dasl/
errors.rs

1//! Structured error types for DASL operations.
2//!
3//! All errors follow the project convention of prefixed error codes
4//! with descriptive messages: `error-atproto-dasl-{domain}-{number}`
5
6use thiserror::Error;
7
8/// Errors that can occur during DAG-CBOR encoding
9#[derive(Debug, Error)]
10pub enum EncodeError {
11    /// I/O error during writing
12    #[error("error-atproto-dasl-encode-1 I/O error: {0}")]
13    Io(#[from] std::io::Error),
14
15    /// Invalid float value (NaN or Infinity)
16    #[error("error-atproto-dasl-encode-2 Invalid float value: {reason}")]
17    InvalidFloat {
18        /// The reason the float is invalid
19        reason: String,
20    },
21
22    /// Indefinite length not supported in DAG-CBOR
23    #[error("error-atproto-dasl-encode-3 Indefinite length encoding not supported")]
24    IndefiniteLengthNotSupported,
25
26    /// Map key was expected but not provided
27    #[error("error-atproto-dasl-encode-4 Map key missing before value")]
28    MapKeyMissing,
29
30    /// Serialization error from serde
31    #[error("error-atproto-dasl-encode-5 Serialization error: {0}")]
32    Serialization(String),
33
34    /// Integer overflow during encoding
35    #[error("error-atproto-dasl-encode-6 Integer overflow: value too large")]
36    IntegerOverflow,
37
38    /// Maximum nesting depth exceeded during encoding
39    #[error("error-atproto-dasl-encode-7 Depth limit exceeded: max {max}")]
40    DepthLimitExceeded {
41        /// The maximum allowed nesting depth.
42        max: usize,
43    },
44
45    /// Output size exceeded during encoding
46    #[error("error-atproto-dasl-encode-8 Output too large: max {max} bytes")]
47    OutputTooLarge {
48        /// The maximum allowed output size in bytes.
49        max: usize,
50    },
51}
52
53impl serde::ser::Error for EncodeError {
54    fn custom<T: std::fmt::Display>(msg: T) -> Self {
55        EncodeError::Serialization(msg.to_string())
56    }
57}
58
59/// Errors that can occur during DAG-CBOR decoding
60#[derive(Debug, Error)]
61pub enum DecodeError {
62    /// I/O error during reading
63    #[error("error-atproto-dasl-decode-1 I/O error: {0}")]
64    Io(#[from] std::io::Error),
65
66    /// Unexpected end of input
67    #[error("error-atproto-dasl-decode-2 Unexpected end of input")]
68    UnexpectedEof,
69
70    /// Invalid CBOR major type
71    #[error("error-atproto-dasl-decode-3 Invalid CBOR structure: {reason}")]
72    InvalidCbor {
73        /// The reason the CBOR is invalid
74        reason: String,
75    },
76
77    /// Non-canonical encoding in strict mode
78    #[error(
79        "error-atproto-dasl-decode-4 Non-canonical encoding: type {major_type}, info {additional_info}, value {value}"
80    )]
81    NonCanonicalEncoding {
82        /// The major type of the non-canonical encoding
83        major_type: u8,
84        /// The additional info byte
85        additional_info: u8,
86        /// The decoded value
87        value: u64,
88    },
89
90    /// Unsupported CBOR tag (only tag 42 allowed)
91    #[error("error-atproto-dasl-decode-5 Unsupported CBOR tag: {tag}")]
92    UnsupportedTag {
93        /// The unsupported tag number
94        tag: u64,
95    },
96
97    /// Map key is not a string (DAG-CBOR requirement)
98    #[error("error-atproto-dasl-decode-6 Map key must be a string")]
99    MapKeyNotString,
100
101    /// Invalid CID encoding
102    #[error("error-atproto-dasl-decode-7 Invalid CID: {reason}")]
103    InvalidCid {
104        /// The reason the CID is invalid
105        reason: String,
106    },
107
108    /// CID tag not followed by byte string
109    #[error("error-atproto-dasl-decode-8 CID tag must be followed by byte string")]
110    InvalidCidContent,
111
112    /// Invalid float value (NaN or Infinity)
113    #[error("error-atproto-dasl-decode-9 Invalid float value: NaN and Infinity not allowed")]
114    InvalidFloat,
115
116    /// Invalid simple value (only false, true, null allowed)
117    #[error("error-atproto-dasl-decode-10 Unsupported simple value: {value}")]
118    UnsupportedSimpleValue {
119        /// The unsupported simple value
120        value: u8,
121    },
122
123    /// Indefinite length not supported
124    #[error("error-atproto-dasl-decode-11 Indefinite length encoding not supported")]
125    IndefiniteLengthNotSupported,
126
127    /// Type mismatch during deserialization
128    #[error("error-atproto-dasl-decode-12 Type mismatch: expected {expected}, found {found}")]
129    TypeMismatch {
130        /// The expected type
131        expected: &'static str,
132        /// The found type
133        found: String,
134    },
135
136    /// Maximum nesting depth exceeded
137    #[error("error-atproto-dasl-decode-13 Maximum nesting depth exceeded: {depth} > {max}")]
138    MaxDepthExceeded {
139        /// The current depth
140        depth: usize,
141        /// The maximum allowed depth
142        max: usize,
143    },
144
145    /// Allocation limit exceeded
146    #[error("error-atproto-dasl-decode-14 Allocation limit exceeded: {size} > {max}")]
147    AllocationExceeded {
148        /// The requested allocation size
149        size: usize,
150        /// The maximum allowed size
151        max: usize,
152    },
153
154    /// Invalid UTF-8 in text string
155    #[error("error-atproto-dasl-decode-15 Invalid UTF-8: {0}")]
156    InvalidUtf8(#[from] std::string::FromUtf8Error),
157
158    /// Non-canonical map key ordering in strict mode
159    #[error("error-atproto-dasl-decode-16 Map keys not in canonical order")]
160    MapKeysNotSorted,
161
162    /// Reserved additional info value (28-30)
163    #[error("error-atproto-dasl-decode-17 Reserved additional info value: {value}")]
164    ReservedAdditionalInfo {
165        /// The reserved value
166        value: u8,
167    },
168
169    /// Deserialization error from serde
170    #[error("error-atproto-dasl-decode-18 Deserialization error: {0}")]
171    Deserialization(String),
172
173    /// Trailing data after top-level object
174    #[error("error-atproto-dasl-decode-19 Trailing data after top-level object")]
175    TrailingData,
176
177    /// Non-canonical float encoding (not 64-bit)
178    #[error("error-atproto-dasl-decode-20 Non-canonical float encoding: expected 64-bit double")]
179    NonCanonicalFloat,
180
181    /// Duplicate map key detected
182    #[error("error-atproto-dasl-decode-21 Duplicate map key")]
183    DuplicateMapKey,
184
185    /// Float values not allowed (no_floats config)
186    #[error("error-atproto-dasl-decode-22 Float values not allowed")]
187    FloatsNotAllowed,
188
189    /// Integer out of i64 range (int64_range_only config)
190    #[error("error-atproto-dasl-decode-23 Integer {value} outside i64 range")]
191    IntegerOutOfRange {
192        /// The value that was out of range
193        value: String,
194    },
195
196    /// Array exceeds maximum element count
197    #[error("error-atproto-dasl-decode-24 Array too large: {count} elements (max {max})")]
198    ArrayTooLarge {
199        /// Actual array length
200        count: usize,
201        /// Maximum allowed length
202        max: usize,
203    },
204
205    /// Map exceeds maximum entry count
206    #[error("error-atproto-dasl-decode-25 Map too large: {count} entries (max {max})")]
207    MapTooLarge {
208        /// Actual map size
209        count: usize,
210        /// Maximum allowed size
211        max: usize,
212    },
213}
214
215impl serde::de::Error for DecodeError {
216    fn custom<T: std::fmt::Display>(msg: T) -> Self {
217        DecodeError::Deserialization(msg.to_string())
218    }
219}
220
221/// Errors during varint encoding/decoding.
222#[derive(Debug, Error)]
223pub enum VarintError {
224    /// Varint exceeds maximum supported size (9 bytes for u64).
225    #[error("error-atproto-dasl-varint-1 Varint overflow: exceeded maximum size")]
226    Overflow,
227
228    /// Unexpected end of input while reading varint.
229    #[error("error-atproto-dasl-varint-2 Unexpected end of input")]
230    UnexpectedEof,
231
232    /// Non-minimal varint encoding detected.
233    #[error("error-atproto-dasl-varint-3 Non-minimal encoding detected")]
234    NonMinimal,
235}
236
237/// Errors during CAR file operations.
238#[derive(Debug, Error)]
239pub enum CarError {
240    /// CAR header is missing or malformed.
241    #[error("error-atproto-dasl-car-1 Invalid CAR header: {reason}")]
242    InvalidHeader {
243        /// Description of why the header is invalid.
244        reason: String,
245    },
246
247    /// CAR version is not supported (only v1 supported).
248    #[error("error-atproto-dasl-car-2 Unsupported CAR version: {version}")]
249    UnsupportedVersion {
250        /// The unsupported version number.
251        version: u64,
252    },
253
254    /// Block CID does not match computed CID.
255    #[error("error-atproto-dasl-car-3 CID mismatch: expected {expected}, got {actual}")]
256    CidMismatch {
257        /// The expected CID from the block header.
258        expected: String,
259        /// The actual CID computed from the block data.
260        actual: String,
261    },
262
263    /// Block data is malformed or truncated.
264    #[error("error-atproto-dasl-car-4 Invalid block data: {reason}")]
265    InvalidBlock {
266        /// Description of why the block is invalid.
267        reason: String,
268    },
269
270    /// DAG-CBOR encoding error.
271    #[error("error-atproto-dasl-car-5 DAG-CBOR encode error: {0}")]
272    DagCborEncode(#[from] EncodeError),
273
274    /// DAG-CBOR decoding error.
275    #[error("error-atproto-dasl-car-6 DAG-CBOR decode error: {0}")]
276    DagCborDecode(DecodeError),
277
278    /// I/O error during CAR operations.
279    #[error("error-atproto-dasl-car-7 I/O error: {0}")]
280    Io(#[from] std::io::Error),
281
282    /// Varint encoding/decoding error.
283    #[error("error-atproto-dasl-car-8 Varint error: {0}")]
284    Varint(#[from] VarintError),
285
286    /// CID parsing error.
287    #[error("error-atproto-dasl-car-9 Invalid CID: {reason}")]
288    InvalidCid {
289        /// Description of why the CID is invalid.
290        reason: String,
291    },
292
293    /// Roots array is empty (at least one root required).
294    #[error("error-atproto-dasl-car-10 CAR file must have at least one root CID")]
295    NoRoots,
296
297    /// CAR file exceeds maximum allowed size.
298    #[error("error-atproto-dasl-car-11 CAR file too large: {size} bytes (max {max})")]
299    FileTooLarge {
300        /// Actual file size in bytes.
301        size: u64,
302        /// Maximum allowed size in bytes.
303        max: u64,
304    },
305
306    /// Storage operation failed.
307    #[error("error-atproto-dasl-car-12 Storage error: {0}")]
308    Storage(#[from] StorageError),
309}
310
311impl From<DecodeError> for CarError {
312    fn from(err: DecodeError) -> Self {
313        CarError::DagCborDecode(err)
314    }
315}
316
317/// Errors during storage operations.
318#[derive(Debug, Error)]
319pub enum StorageError {
320    /// Block not found in storage.
321    #[error("error-atproto-dasl-storage-1 Block not found: {cid}")]
322    BlockNotFound {
323        /// CID of the missing block.
324        cid: String,
325    },
326
327    /// Storage capacity exceeded.
328    #[error("error-atproto-dasl-storage-2 Storage limit exceeded: {limit} bytes")]
329    CapacityExceeded {
330        /// The storage limit that was exceeded.
331        limit: usize,
332    },
333
334    /// I/O error during storage operations.
335    #[error("error-atproto-dasl-storage-3 Storage I/O error: {0}")]
336    Io(#[from] std::io::Error),
337
338    /// Block size exceeds maximum allowed.
339    #[error("error-atproto-dasl-storage-4 Block too large: {size} bytes (max {max})")]
340    BlockTooLarge {
341        /// Actual block size in bytes.
342        size: usize,
343        /// Maximum allowed size in bytes.
344        max: usize,
345    },
346
347    /// Maximum block count exceeded.
348    #[error("error-atproto-dasl-storage-5 Block count exceeded: {count} (max {max})")]
349    BlockCountExceeded {
350        /// Actual block count.
351        count: usize,
352        /// Maximum allowed count.
353        max: usize,
354    },
355
356    /// Depth limit exceeded.
357    #[error("error-atproto-dasl-storage-6 Depth exceeded: {depth} (max {max})")]
358    DepthExceeded {
359        /// Actual depth encountered.
360        depth: usize,
361        /// Maximum allowed depth.
362        max: usize,
363    },
364}
365
366/// Errors during DASL CID construction.
367#[derive(Debug, Error)]
368pub enum DaslCidError {
369    /// CID version is not CIDv1.
370    #[error("error-atproto-dasl-cid-1 DASL requires CIDv1, got CIDv0")]
371    NotCidV1,
372
373    /// Codec is not allowed in DASL.
374    #[error(
375        "error-atproto-dasl-cid-2 Codec 0x{codec:x} not allowed in DASL (must be raw 0x55 or dag-cbor 0x71)"
376    )]
377    InvalidCodec {
378        /// The codec value that was rejected.
379        codec: u64,
380    },
381
382    /// Hash algorithm is not allowed in DASL.
383    #[error(
384        "error-atproto-dasl-cid-3 Hash 0x{hash:x} not allowed in DASL (must be SHA-256 0x12 or BLAKE3 0x1e)"
385    )]
386    InvalidHash {
387        /// The hash algorithm code that was rejected.
388        hash: u64,
389    },
390
391    /// Digest length is not 32 bytes.
392    #[error("error-atproto-dasl-cid-4 Digest length {len} != 32")]
393    InvalidDigestLength {
394        /// The actual digest length.
395        len: usize,
396    },
397
398    /// CID parsing failed.
399    #[error("error-atproto-dasl-cid-5 Invalid CID: {reason}")]
400    InvalidCid {
401        /// Description of the parsing failure.
402        reason: String,
403    },
404}
405
406/// Errors during MASL operations.
407#[derive(Debug, Error)]
408pub enum MaslError {
409    /// Document is missing required fields.
410    #[error("error-atproto-dasl-masl-1 Missing required field: {field}")]
411    MissingField {
412        /// The missing field name.
413        field: String,
414    },
415
416    /// Invalid path format.
417    #[error("error-atproto-dasl-masl-2 Invalid path: {reason}")]
418    InvalidPath {
419        /// Description of why the path is invalid.
420        reason: String,
421    },
422
423    /// Invalid resource configuration.
424    #[error("error-atproto-dasl-masl-3 Invalid resource: {reason}")]
425    InvalidResource {
426        /// Description of what's wrong with the resource.
427        reason: String,
428    },
429
430    /// Bundle and single mode conflict.
431    #[error("error-atproto-dasl-masl-4 Document mode conflict: {reason}")]
432    ModeConflict {
433        /// Description of the conflict.
434        reason: String,
435    },
436
437    /// Invalid CAR format reference.
438    #[error("error-atproto-dasl-masl-5 Invalid CAR format: {reason}")]
439    InvalidCarFormat {
440        /// Description of the issue.
441        reason: String,
442    },
443
444    /// DAG-CBOR encoding/decoding error.
445    #[error("error-atproto-dasl-masl-6 DAG-CBOR error: {0}")]
446    DagCbor(String),
447
448    /// Invalid `$type` field value.
449    #[error("error-atproto-dasl-masl-7 Invalid type: {reason}")]
450    InvalidType {
451        /// Description of the invalid type.
452        reason: String,
453    },
454
455    /// Reference to non-existent resource path.
456    #[error(
457        "error-atproto-dasl-masl-8 Invalid reference: field '{field}' references non-existent path '{path}'"
458    )]
459    InvalidReference {
460        /// The referenced path that doesn't exist.
461        path: String,
462        /// The field containing the reference.
463        field: String,
464    },
465}
466
467/// Errors during RASL operations.
468#[derive(Debug, Error)]
469pub enum RaslError {
470    /// Invalid RASL URL scheme.
471    #[error("error-atproto-dasl-rasl-1 Invalid scheme: expected 'rasl', got '{scheme}'")]
472    InvalidScheme {
473        /// The invalid scheme.
474        scheme: String,
475    },
476
477    /// Missing CID in RASL URL.
478    #[error("error-atproto-dasl-rasl-2 Missing CID in RASL URL")]
479    MissingCid,
480
481    /// Invalid CID in RASL URL.
482    #[error("error-atproto-dasl-rasl-3 Invalid CID: {reason}")]
483    InvalidCid {
484        /// Description of why the CID is invalid.
485        reason: String,
486    },
487
488    /// Invalid hint in RASL URL.
489    #[error("error-atproto-dasl-rasl-4 Invalid hint: {reason}")]
490    InvalidHint {
491        /// Description of why the hint is invalid.
492        reason: String,
493    },
494
495    /// URL parsing failed.
496    #[error("error-atproto-dasl-rasl-5 URL parse error: {0}")]
497    UrlParse(#[from] url::ParseError),
498
499    /// Credentials not allowed in RASL URLs.
500    #[error("error-atproto-dasl-rasl-6 Credentials not allowed in RASL URLs")]
501    CredentialsNotAllowed,
502
503    /// Fragments not allowed in RASL URLs.
504    #[error("error-atproto-dasl-rasl-7 Fragments not allowed in RASL URLs")]
505    FragmentNotAllowed,
506
507    /// All retrieval hints failed.
508    #[error("error-atproto-dasl-rasl-8 All retrieval hints failed")]
509    AllHintsFailed,
510
511    /// CID verification failed after streaming.
512    #[error("error-atproto-dasl-rasl-9 CID verification failed: {reason}")]
513    CidVerification {
514        /// Description of the verification failure.
515        reason: String,
516    },
517
518    /// HTTP request failed.
519    #[cfg(feature = "reqwest")]
520    #[error("error-atproto-dasl-rasl-10 HTTP request failed for {url}: {reason}")]
521    HttpRequest {
522        /// The URL that failed.
523        url: String,
524        /// Description of the failure.
525        reason: String,
526    },
527
528    /// I/O error during RASL operations.
529    #[error("error-atproto-dasl-rasl-11 I/O error: {0}")]
530    Io(#[from] std::io::Error),
531
532    /// No hints provided for fetch.
533    #[error("error-atproto-dasl-rasl-12 No hints provided for fetch")]
534    NoHints,
535
536    /// Directory handler initialization failed.
537    #[error("error-atproto-dasl-rasl-13 Directory handler error: {reason}")]
538    DirectoryError {
539        /// Description of the error.
540        reason: String,
541    },
542}
543
544/// Errors that can occur during Web Tile validation.
545#[derive(Debug, Error)]
546pub enum TilesError {
547    /// The MASL document failed base validation.
548    #[error("error-atproto-dasl-tiles-1 MASL validation failed: {reason}")]
549    MaslValidation {
550        /// Description of the MASL validation failure.
551        reason: String,
552    },
553
554    /// The document is not in bundle mode.
555    #[error("error-atproto-dasl-tiles-2 Tile manifest must be in bundle mode")]
556    NotBundleMode,
557
558    /// The tile manifest is missing a name.
559    #[error("error-atproto-dasl-tiles-3 Tile manifest must have a name")]
560    MissingName,
561
562    /// The tile name exceeds the maximum length.
563    #[error("error-atproto-dasl-tiles-4 Tile name too long: {chars} chars (max {max})")]
564    NameTooLong {
565        /// Actual character count.
566        chars: usize,
567        /// Maximum allowed characters.
568        max: usize,
569    },
570
571    /// The tile manifest is missing a root "/" resource.
572    #[error("error-atproto-dasl-tiles-5 Tile manifest must have a '/' root resource")]
573    MissingRootResource,
574}