ockam_core/error/code.rs
1//! Ockam error code enumerations.
2//!
3//! These are deliberately abstract, and do not cover all possible errors in
4//! detail. Some of the motivation behind this includes:
5//!
6//! 1. Allow code which wishes to categorize, filter, or otherwise handle errors
7//! to do so generically without forcing them to hard-code numeric values.
8//! 2. To avoid each component needing to choose globally unique error numbers.
9use serde::{Deserialize, Serialize};
10
11/// A set of abstract error codes describing an error. See the [module-level
12/// documentation](crate::error::errcode) for details.
13///
14/// The fields of this struct are `pub` for matching, but you need to go through
15/// one of the [constructor functions](ErrorCode::new) to create one of these
16/// (and not a literal), as it is a `#[non_exhaustive]` type (which may change in
17/// the future, since it's unclear if this provides value).
18#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Serialize, Deserialize)]
19#[non_exhaustive]
20pub struct ErrorCode {
21 // Maintenance note: Don't reorder these fields or cfg them out, it's
22 // somewhat important for serialization (at least via BARE) that these are
23 // the same in all configurations -- therefore, all items in this struct
24 // should be reasonable to include on embedded as well (going any higher
25 // than 64 bits seems likely to be too far in that context -- even this much
26 // is perhaps pushing it).
27 /// The [`Origin`] of this error.
28 pub origin: Origin,
29 /// The [`Kind`] of this error.
30 pub kind: Kind,
31 /// An additional identifying numeric payload, or 0 if none is relevant.
32 ///
33 /// For example, it would be reasonable for this field to hold:
34 /// - HTTP status code
35 /// - OS `errno`/`GetLastError` code
36 /// - The exit status returned by a subprocess
37 /// - A numeric error code from some other system
38 /// - Et cetera.
39 ///
40 /// But should generally not be used to hold non-identifying metadata, such
41 /// as the date, device IDs, as that information should be stored on the
42 /// payload itself.
43 ///
44 /// Concretely: two `ErrorCode` with different `extra` values should
45 /// identify types of errors.
46 // TODO: is 32 bits okay on embedded? This puts us to 64 bits for the
47 // structure in practice, but means that we'll always be able to hold OS
48 // errors, as well as `old_error::Error::code`.
49 pub extra: i32,
50}
51
52impl ErrorCode {
53 /// Construct the `ErrorCode` for an error.
54 #[cold]
55 pub fn new(origin: Origin, kind: Kind) -> Self {
56 Self {
57 origin,
58 kind,
59 extra: 0,
60 }
61 }
62 /// Construct the `ErrorCode` for an error which contains an additional
63 /// numeric payload.
64 #[cold]
65 pub fn new_with_extra(origin: Origin, kind: Kind, extra: i32) -> Self {
66 Self {
67 origin,
68 kind,
69 extra,
70 }
71 }
72
73 /// Construct an error code with very little useful information
74 #[cold]
75 pub fn unknown() -> Self {
76 Self {
77 origin: Origin::Unknown,
78 kind: Kind::Unknown,
79 extra: 0,
80 }
81 }
82
83 /// Attach an origin and/or kind to the error, without risk of overwriting more
84 /// precise information value.
85 #[must_use]
86 pub fn update_unknown(
87 mut self,
88 o: impl Into<Option<Origin>>,
89 k: impl Into<Option<Kind>>,
90 ) -> Self {
91 if let (Origin::Unknown, Some(o)) = (self.origin, o.into()) {
92 self.origin = o;
93 }
94 if let (Kind::Unknown, Some(k)) = (self.kind, k.into()) {
95 self.kind = k;
96 }
97 self
98 }
99}
100
101/// Origin indicates the abstract source of an error.
102///
103/// Note that [`Error`](super::Error) should already contain precise origin
104/// information (file, line) where the error originated from.
105///
106// Internal note: Once we stabilise the API, we should not remove these, just stop emitting them.
107#[repr(u8)]
108#[non_exhaustive]
109#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Serialize, Deserialize)]
110pub enum Origin {
111 /// An error for which there is no way to determine a more specific origin.
112 ///
113 /// Eventually this should also be used for errors which, during
114 /// deserialization, have an unknown `Origin` (for now this is too
115 /// error-prone for various reasons).
116 Unknown = 0,
117 /// Reserved for errors emitted by applications using ockam.
118 Application = 1,
119 /// An error originating from the vault.
120 Vault = 2,
121 /// Errors emitted by the transport layer.
122 Transport = 3,
123 /// Errors from some part of the node implementation — the router or relay,
124 /// for example.
125 Node = 4,
126 /// Errors from the surface API — for example: the FFI layer.
127 Api = 5,
128 /// Errors from within the identity-management code.
129 Identity = 6,
130 /// Errors from the secure channel implementation.
131 Channel = 7,
132 /// Errors occurring from the one of the key exchange implementations.
133 KeyExchange = 8,
134 /// An error which occurs in the executor (e.g. `ockam_executor`, since
135 /// `tokio` errors will likely come from elsewhere).
136 Executor = 9,
137 /// Other errors from within `ockam` or `ockam_core`.
138 Core = 10,
139 /// Ockam protocol crate
140 Ockam = 11,
141 /// Errors from within the Ockam authorization code.
142 Authorization = 12,
143 /// Errors from other sources, such as libraries extending `ockam`.
144 ///
145 /// Note: The actual source (file, line, ...) will (hopefully) be available
146 /// on the error itself, as one of the members of the payload.
147 Other = 13,
148 // This is a `#[non_exhaustive]` enum — we're free to add more variants
149 // here. Do not add any which contain payloads (it should stay a "C style
150 // enum"). Payload information should be added to the error itself.
151}
152
153/// Category indicates "what went wrong", in abstract terms.
154///
155/// # Choosing a `Kind`
156///
157/// - [`Kind::Io`], [`Kind::Protocol`], and [`Kind::Other`] should only be used
158/// if there's no more specific option.
159///
160/// For example, a network timeout is a type of IO error, however it should
161/// use [`Kind::Timeout`] rather than [`Kind::Io`].
162///
163/// - [`Kind::Invalid`] should be used when the input will never be valid (at
164/// least in this version of the software), rather than input which is invalid
165/// because of the current system state.
166///
167/// For example, an unknown identifier should use [`Kind::NotFound`] (for
168/// example `ockam_vault_core`'s `Secret`) rather than [`Kind::Invalid`],
169///
170/// - [`Kind::Cancelled`], [`Kind::Timeout`], and [`Kind::Shutdown`] all sound
171/// similar, but:
172///
173/// - [`Kind::Timeout`] should be used to map operations which timeout
174/// externally, such as network requests. These may succeed if retried.
175///
176/// - [`Kind::Shutdown`] should be used to indicate the operation failed due
177/// to the node shutting down.
178///
179/// - [`Kind::Cancelled`] is used when a request to cancel the operation
180/// comes in while the operation is in progress.
181#[repr(u8)]
182#[non_exhaustive]
183#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
184pub enum Kind {
185 /// Indicates that there is no way to determine a more specific kind.
186 // Internal note: We should not emit this from within the
187 // `build-trust/ockam` crates.
188 Unknown = 0,
189
190 /// Used for serious internal errors, including panics.
191 ///
192 /// This generally should not be used unless we'd accept a bug report for
193 /// the error.
194 Internal = 1,
195
196 /// The input was fundamentally invalid.
197 ///
198 /// For example, this is appropriate to use when:
199 ///
200 /// - A null pointer being passed as input in the FFI.
201 /// - A string is used as input which is not UTF-8.
202 /// - Parse failures of various kinds, but be sure to include more specific
203 /// information in the [`Error`](crate::Error) payload.
204 ///
205 /// Note that it is not appropriate for input which is invalid only due to
206 /// the current system state.
207 // Internal note: Check if there's a more specific `Kind` before using this
208 // — it should mostly be `Origin
209 Invalid = 2,
210
211 /// The requested operation is not supported/implemented by that component.
212 ///
213 /// For example, this is appropriate for a component (for example, a custom
214 /// Vault) which do not implement the entire API surface.
215 Unsupported = 3,
216
217 /// Some referenced entity was not found.
218 ///
219 /// For example, this may be appropriate for:
220 ///
221 /// - A vault which does not recognize a `Secret` which it receives.
222 /// - FFI that receives an integer `handle` that does not belong to any
223 /// known entity.
224 /// - Local [`Address`](crate::Address) which don't correspond to any known
225 /// `Worker` or `Processor`.
226 /// - [`Address`](crate::Address) with a transport of an unknown or
227 /// unsupported type.
228 ///
229 /// Information about what exactly it is that could not be located should be
230 /// available on the [`Error`](crate::Error) itself.
231 NotFound = 4,
232
233 /// The operation failed because a resource already exists.
234 ///
235 /// For example, this may be appropriate for:
236 ///
237 /// - A ockam node already exists
238 /// - A vault state already exists
239 /// - An entry in a cddl file that already has an entry for a given key.
240 /// This would indicate that a resource has been defined multiple times,
241 /// possibly in different wasys.
242 ///
243 /// Information about what exactly it is that already existed should be
244 /// available on the [`Error`](crate::Error) itself.
245 AlreadyExists = 5,
246
247 /// Indicates that some resource has been exhausted, or would be exhausted
248 /// if the request were to be fulfilled.
249 ///
250 /// The resource in question could be memory, open file descriptors,
251 /// storage, quota, simultaneous in-flight messages or tasks...
252 ///
253 /// Information about which resource it was that was exhausted should be
254 /// available on the [`Error`](crate::Error) itself.
255 ResourceExhausted = 6,
256
257 /// An API was misused in some unspecific fashion.
258 ///
259 /// This is mostly intended for FFI and other non-Rust bindings — for
260 /// example, it would be appropriate to map [`core::cell::BorrowError`] to
261 /// this.
262 // Internal note: Check if there's a more specific `Kind` before using this.
263 Misuse = 7,
264
265 /// Indicates the operation failed due to a cancellation request.
266 ///
267 /// See the type documentation on the difference between this,
268 /// [`Kind::Shutdown`] and [`Kind::Timeout`].
269 Cancelled = 8,
270
271 /// Indicates that the operation failed due to the node shutting down.
272 ///
273 /// See the type documentation on the difference between this,
274 /// [`Kind::Cancelled`] and [`Kind::Timeout`].
275 Shutdown = 9,
276
277 /// Indicates that the operation failed due to an external operation timing
278 /// out, such as a network request, which may succeed if retried.
279 ///
280 /// See the type documentation on the difference between this,
281 /// [`Kind::Shutdown`] and [`Kind::Cancelled`].
282 Timeout = 10,
283
284 /// Indicates an operation failed due to simultaneous attempts to modify a
285 /// resource.
286 Conflict = 11,
287
288 /// Indicates an a failure to deserialize a message (or in rare cases,
289 /// failure to serialize).
290 Serialization = 12,
291
292 /// Indicates a parsing error.
293 Parse = 13,
294
295 /// Indicates some other I/O error.
296 ///
297 /// Specifics should be available on error payload.
298 // Internal note: Check if there's a more specific `Kind` before using this.
299 Io = 14,
300
301 /// Indicates some other I/O error.
302 ///
303 /// Specifics should be available on error payload.
304 // Internal note: Check if there's a more specific `Kind` before using this.
305 Protocol = 15,
306
307 /// Indicates an error that
308 ///
309 /// Specifics should be available on error payload.
310 // Internal note: Check if there's a more specific `Kind` before using this.
311 Other = 16,
312 // This is a `#[non_exhaustive]` enum — we're free to add more variants
313 // here. Do not add any which contain payloads (it should stay a "C style
314 // enum"). Payload information should be added to the error itself.
315 //
316 // That said, until we finish migrating over to this, it's expected that
317 // we'll need to add several new variants to all of these.
318 /// Indicate that the resource is not ready yet.
319 ///
320 /// Specifics should be available on error payload.
321 NotReady = 17,
322}
323
324// Helper macro for converting a number into an enum variant with that value.
325// Variants do not need to be contiguous. Requires listing the error variants
326// again, but forces a compile-time error if the list is missing a variant.
327macro_rules! from_prim {
328 ($prim:expr, $typ:tt => $Enum:ident { $($Variant:ident),* $(,)? }) => {{
329 // Force a compile error if the list gets out of date.
330 const _: fn(e: $Enum) = |e: $Enum| match e {
331 $($Enum::$Variant => ()),*
332 };
333 match $prim {
334 $(v if v == ($Enum::$Variant as $typ) => Some($Enum::$Variant),)*
335 _ => None,
336 }
337 }};
338}
339
340impl Origin {
341 /// Attempt to convert a numeric value into an `Origin`.
342 ///
343 /// `From<u8>` is also implemented, replacing unknown inputs with
344 /// `Self::Unknown`.
345 #[track_caller]
346 pub fn from_u8(n: u8) -> Option<Self> {
347 from_prim!(n, u8 => Origin {
348 Unknown,
349 Application,
350 Vault,
351 Transport,
352 Node,
353 Api,
354 Identity,
355 Channel,
356 KeyExchange,
357 Executor,
358 Core,
359 Ockam,
360 Authorization,
361 Other,
362 })
363 }
364}
365
366impl From<u8> for Origin {
367 #[track_caller]
368 fn from(src: u8) -> Self {
369 match Self::from_u8(src) {
370 Some(n) => n,
371 None => {
372 warn!("Unknown error origin: {}", src);
373 Self::Unknown
374 }
375 }
376 }
377}
378
379impl Kind {
380 /// Attempt to construct a `Kind` from the numeric value.
381 pub fn from_u8(n: u8) -> Option<Self> {
382 from_prim!(n, u8 => Kind {
383 Unknown,
384 Internal,
385 Invalid,
386 Unsupported,
387 NotFound,
388 AlreadyExists,
389 ResourceExhausted,
390 Misuse,
391 Cancelled,
392 Shutdown,
393 Timeout,
394 Conflict,
395 Io,
396 Protocol,
397 Serialization,
398 Other,
399 Parse,
400 NotReady
401 })
402 }
403}
404
405impl From<u8> for Kind {
406 #[track_caller]
407 fn from(src: u8) -> Self {
408 match Self::from_u8(src) {
409 Some(n) => n,
410 None => {
411 warn!("Unknown error origin: {}", src);
412 Self::Unknown
413 }
414 }
415 }
416}
417
418impl core::fmt::Display for ErrorCode {
419 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
420 write!(f, "origin: {:?}, kind: {:?}", self.origin, self.kind,)?;
421 if self.extra != 0 {
422 write!(f, ", code = {}", self.extra)?
423 };
424 Ok(())
425 }
426}