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