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