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}