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