coven_format/error.rs
1//! Failures to encode or decode a format object.
2
3/// An invariant a format object did not meet.
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5pub enum Rule {
6 /// A text code has the wrong prefix, or a frame has an unexpected kind.
7 Kind,
8 /// Base32 length or unused trailing bits are not canonical.
9 CodePadding,
10 /// A required name, collection or number was empty or zero.
11 Required,
12 /// A set was not in strictly increasing order, or repeated an identity.
13 Order,
14 /// A removed row is absent from merged state.
15 Generation,
16 /// A timestamp named a different device than its write or entry.
17 TimestampDevice,
18 /// A write names a different device than its containing slot.
19 WriteDevice,
20 /// A device registration replaces the same id it adds.
21 DeviceReplacement,
22 /// An admission's key boxes name more than one recipient or sealing key.
23 Recipient,
24 /// An admission registers the device that writes it.
25 AdmittedDevice,
26 /// A part, row or removal rule named an audience it cannot contain or describe.
27 Audience,
28 /// A reported storage path belongs to a different store.
29 Store,
30 /// Old and new column values did not match the operation.
31 ColumnOperation,
32 /// A foreign key names different numbers of source and target columns.
33 ForeignKeyColumns,
34 /// A real number was NaN or encoded negative zero.
35 Real,
36 /// A row key contained null.
37 NullKey,
38 /// A key has a noncanonical numeric or escaped byte representation.
39 KeyEncoding,
40 /// A snapshot record or dismissal names a write outside its covered or read-through slot.
41 Coverage,
42 /// Snapshot sections, counts or their end marker did not match the header.
43 SnapshotSequence,
44 /// A stream's declared byte length or record count disagrees with its frames.
45 StreamLength,
46 /// A chunk's index or byte length did not match its file header.
47 Chunk,
48 /// A storage time's fraction is one second or more.
49 Nanoseconds,
50 /// A storage time precedes the Unix epoch.
51 Range,
52 /// A raise snapshot's schema version is not the version its prefix raises to.
53 RaiseVersion,
54}
55
56/// A typed failure, preserving the field and the failed structural requirement.
57#[derive(Debug, PartialEq, Eq)]
58pub enum Error {
59 /// Merge rejected a row change, written reference or decoded row state.
60 Merge(coven_merge::MergeError),
61 /// Crypto rejected encoded secret material.
62 Material(coven_crypto::MaterialError),
63 /// Crypto rejected an invalid or weak Ed25519 signing key.
64 InvalidSigningKey,
65 /// More bytes were required to finish the object.
66 Truncated,
67 /// Bytes remained after the end of an object.
68 TrailingBytes,
69 /// The object's format version is not supported.
70 UnsupportedVersion(u16),
71 /// A kind or variant tag is unknown.
72 UnknownTag {
73 /// The field containing the tag.
74 field: &'static str,
75 /// The unrecognized tag.
76 tag: u8,
77 },
78 /// A length exceeds the format's bound.
79 Limit {
80 /// The field or resource whose bound was exceeded.
81 field: &'static str,
82 /// The declared or requested length.
83 actual: usize,
84 /// The largest accepted length.
85 maximum: usize,
86 },
87 /// A string was not UTF-8.
88 Utf8,
89 /// An object failed a structural check.
90 Invalid {
91 /// The field that failed the check.
92 field: &'static str,
93 /// The violated requirement.
94 rule: Rule,
95 },
96 /// A typed code contained a character outside its uppercase base32 alphabet.
97 CodeCharacter {
98 /// The byte offset of the character.
99 offset: usize,
100 },
101 /// A typed code's checksum did not match its contents.
102 Checksum,
103 /// Memory could not be reserved within the format's bounds.
104 Allocation,
105}
106
107impl std::fmt::Display for Error {
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 match self {
110 Self::Merge(e) => write!(f, "invalid merge input: {e}"),
111 Self::Material(e) => write!(f, "invalid secret material: {e}"),
112 Self::InvalidSigningKey => f.write_str("invalid or weak signing key"),
113 Self::Truncated => f.write_str("object is truncated"),
114 Self::TrailingBytes => f.write_str("bytes follow the object's end"),
115 Self::UnsupportedVersion(v) => write!(f, "unsupported format version {v}"),
116 Self::UnknownTag { field, tag } => write!(f, "unknown {field} tag {tag}"),
117 Self::Limit { field, actual, maximum } => {
118 write!(f, "{field} length {actual} exceeds {maximum}")
119 }
120 Self::Utf8 => f.write_str("text is not UTF-8"),
121 Self::Invalid { field, rule } => write!(f, "{field} violates {rule:?}"),
122 Self::CodeCharacter { offset } => write!(f, "invalid code character at byte {offset}"),
123 Self::Checksum => f.write_str("code checksum does not match"),
124 Self::Allocation => f.write_str("could not reserve memory for the object"),
125 }
126 }
127}
128
129impl std::error::Error for Error {
130 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
131 match self {
132 Self::Merge(e) => Some(e),
133 Self::Material(e) => Some(e),
134 _ => None,
135 }
136 }
137}
138
139/// A merge check that failed while decoding is `Merge`.
140impl From<coven_merge::MergeError> for Error {
141 fn from(error: coven_merge::MergeError) -> Self {
142 Self::Merge(error)
143 }
144}
145
146/// A failed reservation is `Allocation`. Its layout is not kept: the request
147/// is within the format's own bounds, so only the memory itself was missing.
148impl From<std::collections::TryReserveError> for Error {
149 fn from(_: std::collections::TryReserveError) -> Self {
150 Self::Allocation
151 }
152}
153
154/// Text that isn't UTF-8 is `Utf8`, wherever its first bad byte is.
155impl From<std::str::Utf8Error> for Error {
156 fn from(_: std::str::Utf8Error) -> Self {
157 Self::Utf8
158 }
159}
160
161/// Text that isn't UTF-8 is `Utf8`, wherever its first bad byte is.
162impl From<std::string::FromUtf8Error> for Error {
163 fn from(_: std::string::FromUtf8Error) -> Self {
164 Self::Utf8
165 }
166}
167
168pub(crate) fn require(ok: bool, field: &'static str, rule: Rule) -> Result<(), Error> {
169 if ok {
170 Ok(())
171 } else {
172 Err(Error::Invalid { field, rule })
173 }
174}
175
176/// Refuse a length other than `expected`: shorter is truncated, longer has
177/// trailing bytes.
178pub(crate) fn exact_length<T: Ord>(actual: T, expected: T) -> Result<(), Error> {
179 match actual.cmp(&expected) {
180 std::cmp::Ordering::Less => Err(Error::Truncated),
181 std::cmp::Ordering::Greater => Err(Error::TrailingBytes),
182 std::cmp::Ordering::Equal => Ok(()),
183 }
184}
185
186pub(crate) fn bound(n: usize, maximum: usize, field: &'static str) -> Result<(), Error> {
187 if n <= maximum {
188 Ok(())
189 } else {
190 Err(Error::Limit { field, actual: n, maximum })
191 }
192}