Skip to main content

datumesh_core/
error.rs

1//! Error codes (SPEC 10.2) and the error type every fallible function returns.
2
3use core::fmt;
4
5macro_rules! codes {
6    ($( $(#[$doc:meta])* $variant:ident = $text:literal, )*) => {
7        /// One variant per `E_*` code of SPEC 10.2 (`spec-0.4.0`).
8        /// Non-exhaustive: codes added by later revisions of the specification are additions.
9        ///
10        /// The variants are declared in the order of 10.2, except the codes added within
11        /// 0.3.x, which follow in the order they were added so that [`ErrorCode::ALL`] and the
12        /// derived ordering only grow: `EvalUnavailable` (0.3.0; 10.2 lists it between
13        /// `E_VALUE_MISMATCH` and `E_BLOB_MISMATCH`), then `SliceMember` and `InputSuperseded`
14        /// (0.3.4, `spec-0.4.0`; 10.2 lists them after `E_INPUT_UNDECLARED` and after
15        /// `E_EVAL_UNAVAILABLE`). The formula codes of SPEC 15.11 are not verification codes
16        /// and have no variant; the envelope's `fault` is a [`crate::Fault`].
17        #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
18        #[non_exhaustive]
19        pub enum ErrorCode {
20            $( $(#[$doc])* $variant, )*
21        }
22
23        impl ErrorCode {
24            /// Every code, in the order of SPEC 10.2 except the codes added within 0.3.x, which
25            /// come last (see [`ErrorCode`]).
26            pub const ALL: &'static [ErrorCode] = &[$(ErrorCode::$variant,)*];
27
28            /// The code as written in the specification, e.g. `"E_SIGNATURE"`.
29            pub const fn as_str(self) -> &'static str {
30                match self { $(ErrorCode::$variant => $text,)* }
31            }
32
33            /// Looks a code up by its text; `None` for anything not in [`ErrorCode::ALL`].
34            pub fn from_code(text: &str) -> Option<ErrorCode> {
35                match text { $($text => Some(ErrorCode::$variant),)* _ => None }
36            }
37        }
38    };
39}
40
41codes! {
42    /// Not strict JSON (6.1).
43    ParseJson = "E_PARSE_JSON",
44    /// A member name repeated in one object.
45    ParseDuplicateKey = "E_PARSE_DUPLICATE_KEY",
46    /// Not a cell URI by the outer grammar (5.1).
47    ParseUri = "E_PARSE_URI",
48    /// Missing or extra member, wrong JSON type, wrong `proto`, null; content for a non-blob.
49    Schema = "E_SCHEMA",
50    /// Invalid DNS name, namespace, function name, code language or key reference.
51    CanonName = "E_CANON_NAME",
52    /// Invalid or reserved argument name.
53    CanonArgName = "E_CANON_ARG_NAME",
54    /// An argument or `asof` repeated in a URI.
55    CanonArgDuplicate = "E_CANON_ARG_DUPLICATE",
56    /// URI arguments out of order, or `asof` not last.
57    CanonArgOrder = "E_CANON_ARG_ORDER",
58    /// Non-canonical percent-encoding or non-UTF-8 bytes.
59    CanonPercent = "E_CANON_PERCENT",
60    /// Unknown type name, value of the wrong JSON type, or a bad valid-time declaration.
61    CanonType = "E_CANON_TYPE",
62    /// Number not in canonical decimal form.
63    CanonNumber = "E_CANON_NUMBER",
64    /// A unit rule of 7.1 or `units.md` broken.
65    CanonUnit = "E_CANON_UNIT",
66    /// Date not `YYYY-MM-DD` or not a calendar day.
67    CanonDate = "E_CANON_DATE",
68    /// Instant not `YYYY-MM-DDTHH:MM:SS.ffffffZ` or not a real time.
69    CanonInstant = "E_CANON_INSTANT",
70    /// Control, unassigned or surrogate code point, not NFC, or empty summary.
71    CanonString = "E_CANON_STRING",
72    /// Blob not `<sha256>:<length>:<media type>`.
73    CanonBlob = "E_CANON_BLOB",
74    /// Hash, key id, signature or slice entry not lowercase hex of the right length.
75    CanonHex = "E_CANON_HEX",
76    /// Licence neither an SPDX identifier nor an `https://` URL.
77    CanonLicence = "E_CANON_LICENCE",
78    /// `inputs`, an extent or manifest members not strictly ascending; `order` not every
79    /// argument exactly once.
80    CanonSet = "E_CANON_SET",
81    /// A limit from 4.5 exceeded.
82    CanonLimit = "E_CANON_LIMIT",
83    /// A slice marker where a single cell is required; a slice object without an open or
84    /// ranged argument.
85    Slice = "E_SLICE",
86    /// `publisher` differs from `cell.ns`.
87    PublisherMismatch = "E_PUBLISHER_MISMATCH",
88    /// `valid` does not echo exactly the valid-time arguments.
89    ValidMismatch = "E_VALID_MISMATCH",
90    /// A time-order rule broken.
91    TimeOrder = "E_TIME_ORDER",
92    /// An argument the function does not declare.
93    ArgUnknown = "E_ARG_UNKNOWN",
94    /// A declared argument not bound.
95    ArgMissing = "E_ARG_MISSING",
96    /// The key document could not be retrieved.
97    KeyUnavailable = "E_KEY_UNAVAILABLE",
98    /// The key document is malformed or breaks a rule of 9.2.
99    KeyDoc = "E_KEY_DOC",
100    /// The key document has no key with that id.
101    KeyUnknown = "E_KEY_UNKNOWN",
102    /// `issued` is outside the key's validity window.
103    KeyNotValid = "E_KEY_NOT_VALID",
104    /// The Ed25519 signature does not verify.
105    Signature = "E_SIGNATURE",
106    /// No descriptor found for `fn_hash`.
107    FnUnavailable = "E_FN_UNAVAILABLE",
108    /// The descriptor does not hash to `fn_hash`.
109    FnHashMismatch = "E_FN_HASH_MISMATCH",
110    /// An argument's type differs from its declaration.
111    ArgType = "E_ARG_TYPE",
112    /// `type` differs from the declared return type.
113    ReturnType = "E_RETURN_TYPE",
114    /// An input receipt cannot be found, or has another hash.
115    InputUnavailable = "E_INPUT_UNAVAILABLE",
116    /// An input receipt is not signature-valid.
117    InputInvalid = "E_INPUT_INVALID",
118    /// An input receipt, or a slice, is of a function not in the descriptor's `inputs`.
119    InputUndeclared = "E_INPUT_UNDECLARED",
120    /// Recomputation gives a different type or value.
121    ValueMismatch = "E_VALUE_MISMATCH",
122    /// Blob content has another length or hash than the blob states.
123    BlobMismatch = "E_BLOB_MISMATCH",
124    /// A response envelope breaks a rule of 5.5.
125    Envelope = "E_ENVELOPE",
126    /// The verifier could not evaluate the function (10.1 step 7.6): no runtime for its
127    /// language, a budget exceeded, or inputs the evaluator cannot obtain. Not a failure: it
128    /// is reported with success at level `signed` (see [`crate::VerifyResult`]). A mesh may
129    /// carry it in an `error` envelope (5.5). In 10.2 since `spec-0.3.0` (RS-28).
130    EvalUnavailable = "E_EVAL_UNAVAILABLE",
131    /// A member receipt does not belong to its slice, or two members of a membership slice
132    /// are one cell (10.1 step 7.5, `spec-0.4.0`).
133    SliceMember = "E_SLICE_MEMBER",
134    /// Recomputation read a later version of an input cell, or other members of an input
135    /// slice (10.1 step 7.6, `spec-0.4.0`): the receipt is historical, not wrong. Not a
136    /// failure: it is reported with success at level `signed`, at path `$.inputs` (see
137    /// [`crate::VerifyResult`]).
138    InputSuperseded = "E_INPUT_SUPERSEDED",
139}
140
141impl fmt::Display for ErrorCode {
142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143        f.write_str(self.as_str())
144    }
145}
146
147/// A failure: its code, the JSON path (or URI part) where it was found, and a human detail.
148#[derive(Clone, Debug, PartialEq, Eq)]
149pub struct Error {
150    code: ErrorCode,
151    path: String,
152    detail: String,
153}
154
155impl Error {
156    /// Builds an error. `path` is a JSONPath-like location such as `$.cell.args.base`.
157    pub fn new(code: ErrorCode, path: impl Into<String>, detail: impl Into<String>) -> Error {
158        Error { code, path: path.into(), detail: detail.into() }
159    }
160
161    /// The code of SPEC 10.2.
162    pub fn code(&self) -> ErrorCode {
163        self.code
164    }
165
166    /// The code as text, e.g. `"E_SIGNATURE"`.
167    pub fn code_str(&self) -> &'static str {
168        self.code.as_str()
169    }
170
171    /// Where the failure was found: `$` is the whole input, `$.a.b[0]` a member.
172    pub fn path(&self) -> &str {
173        &self.path
174    }
175
176    /// A human-readable explanation. Not stable; do not match on it.
177    pub fn detail(&self) -> &str {
178        &self.detail
179    }
180
181    /// The same failure reported under another code (e.g. wrapping as `E_KEY_DOC`).
182    pub(crate) fn recode(self, code: ErrorCode) -> Error {
183        let detail = if self.code == code { self.detail } else { format!("{}: {}", self.code, self.detail) };
184        Error { code, path: self.path, detail }
185    }
186}
187
188impl fmt::Display for Error {
189    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190        write!(f, "{} at {}: {}", self.code, self.path, self.detail)
191    }
192}
193
194impl std::error::Error for Error {}
195
196/// Shorthand used throughout the crate.
197pub type Result<T> = core::result::Result<T, Error>;
198
199pub(crate) fn err<T>(code: ErrorCode, path: &str, detail: impl Into<String>) -> Result<T> {
200    Err(Error::new(code, path, detail))
201}
202
203/// Appends a member name to a path.
204pub(crate) fn member(path: &str, name: &str) -> String {
205    format!("{path}.{name}")
206}
207
208/// Appends an array index to a path.
209pub(crate) fn index(path: &str, i: usize) -> String {
210    format!("{path}[{i}]")
211}