Skip to main content

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}