offline_protocol_data/error.rs
1//! Errors raised by the data layer.
2
3use thiserror::Error;
4
5/// Result alias for data-layer operations.
6pub type DataResult<T> = std::result::Result<T, DataError>;
7
8/// A failure inside the replicated-document layer.
9///
10/// Every variant is either a caller mistake (a bad key, an out-of-range
11/// position, a document over the cap) or a permanent corruption verdict.
12/// Transient conditions do not appear here: persistence retries live above
13/// this crate, on the SDK's storage seam.
14#[derive(Debug, Error)]
15#[non_exhaustive]
16pub enum DataError {
17 /// A document, snapshot, or delta could not be decoded.
18 ///
19 /// The bytes reaching this crate always come back out of a sealed
20 /// protocol-state record, so an AEAD tag has already vouched for them.
21 /// This variant therefore means the engine rejected its own output:
22 /// treat the document as permanently unreadable rather than retrying.
23 #[error("document data is corrupt: {0}")]
24 Corrupt(String),
25
26 /// A document handle was used after a decode panic poisoned it.
27 ///
28 /// Held separate from [`DataError::Corrupt`] because the failure is
29 /// sticky: every later call on the same handle answers this, and the
30 /// only recovery is to drop the handle and re-open from storage.
31 #[error("document handle is poisoned by an earlier decode failure")]
32 Poisoned,
33
34 /// The compacted document would exceed [`crate::MAX_DOC_BYTES`].
35 ///
36 /// Raised at commit, never as a silent truncation: an application that
37 /// keeps typing into a full document has to hear about it while the
38 /// text is still on screen.
39 #[error("document is {actual} bytes compacted, over the {limit} byte cap")]
40 DocTooLarge {
41 /// Size of the compacted encoding that breached the cap.
42 actual: usize,
43 /// The cap in force, [`crate::MAX_DOC_BYTES`].
44 limit: usize,
45 },
46
47 /// A single value is larger than [`crate::MAX_VALUE_BYTES`].
48 ///
49 /// Refused at the operation rather than at commit. A value this size can
50 /// never fit in a document, and discovering that at commit means the
51 /// document's next delta record is unwritable and stays unwritable,
52 /// because a refused write is re-exported (larger) by the commit after it.
53 #[error("value is {actual} bytes, over the {limit} byte limit")]
54 ValueTooLarge {
55 /// Size of the rejected value.
56 actual: usize,
57 /// The limit in force, [`crate::MAX_VALUE_BYTES`].
58 limit: usize,
59 },
60
61 /// A collection or document name is empty, over-long, or uses characters
62 /// outside the accepted set.
63 #[error("invalid name {name:?}: {reason}")]
64 InvalidName {
65 /// The rejected name.
66 name: String,
67 /// Why it was rejected.
68 reason: &'static str,
69 },
70
71 /// An attachment reference is malformed.
72 ///
73 /// Separate from [`DataError::InvalidName`] because an attachment hash
74 /// is not a name the caller chose, it is the address of the bytes. A
75 /// reference whose hash is not a SHA-256 can never be fetched, and a
76 /// reference that cannot be fetched is worse than no reference at all:
77 /// it replicates to every member of the space, and every one of them
78 /// sees an attachment that will never arrive.
79 #[error("invalid attachment: {reason}")]
80 InvalidAttachment {
81 /// Why it was rejected.
82 reason: &'static str,
83 },
84
85 /// A list or text position is outside the current length.
86 #[error("position {position} is out of range for length {length}")]
87 OutOfRange {
88 /// The position the caller asked for.
89 position: usize,
90 /// The current length of the collection.
91 length: usize,
92 },
93
94 /// The engine refused an otherwise well-formed operation.
95 #[error("data operation failed: {0}")]
96 Engine(String),
97}