rich_err/
error_code.rs

1/// The type returned by [`ToErrorCode::code`].
2pub type ErrorCode = u16;
3
4/// Provides a non-ambiguous way to retrieve the [error code](ErrorCode) associated with a given
5/// error.
6///
7/// An error code is meant to be a precise ID that helps users locate detailed documentation of the
8/// error.
9pub trait ToErrorCode {
10    /// Returns the [error code](ErrorCode) associated with this error.
11    ///
12    /// If an application only requires one error type (and that type happens to be a unit `enum`),
13    /// an implementation like this might be sufficient:
14    ///
15    /// ```rust
16    /// # use rich_err::error_code::{ErrorCode, ToErrorCode};
17    /// #[derive(Clone, Copy)]
18    /// pub enum MyError {
19    ///     Foo,
20    ///     Bar,
21    ///     Baz,
22    /// }
23    ///
24    /// impl ToErrorCode for MyError {
25    ///     fn code(&self) -> ErrorCode {
26    ///         *self as ErrorCode
27    ///     }
28    /// }
29    /// ```
30    ///
31    /// For more complex situations, it may make more sense to give each error type its own "prefix"
32    /// and add that to the discriminant.
33    ///
34    /// ```rust
35    /// # use rich_err::error_code::{ErrorCode, ToErrorCode};
36    /// #[derive(Clone, Copy)]
37    /// pub enum ErrorA {
38    ///     Foo,
39    ///     Bar,
40    ///     Baz,
41    /// }
42    ///
43    /// #[derive(Clone, Copy)]
44    /// pub enum ErrorB {
45    ///     Qux,
46    ///     Quux,
47    /// }
48    ///
49    /// #[derive(Clone, Copy)]
50    /// pub enum ErrorC {
51    ///     Fizz,
52    ///     Buzz,
53    /// }
54    ///
55    /// impl ToErrorCode for ErrorA {
56    ///     fn code(&self) -> ErrorCode {
57    ///         const PREFIX: ErrorCode = 0;
58    ///         PREFIX + *self as ErrorCode
59    ///     }
60    /// }
61    ///
62    /// impl ToErrorCode for ErrorB {
63    ///     fn code(&self) -> ErrorCode {
64    ///         const PREFIX: ErrorCode = 100;
65    ///         PREFIX + *self as ErrorCode
66    ///     }
67    /// }
68    ///
69    /// impl ToErrorCode for ErrorC {
70    ///     fn code(&self) -> ErrorCode {
71    ///         const PREFIX: ErrorCode = 200;
72    ///         PREFIX + *self as ErrorCode
73    ///     }
74    /// }
75    /// ```
76    ///
77    /// With this approach, the prefixes must be large enough to accomodate all error variants in
78    /// each type. Otherwise, multiple errors might have the same [error code](ErrorCode).
79    ///
80    /// The previous two approaches rely on errors being unit `enum`s. Although this can work in
81    /// some situations, it is not uncommon for error types to have tuple and/or struct variants as
82    /// well. In these situations, it is still possible (albeit more difficult) to automatically
83    /// retrieve the discriminant if the `enum` uses a [primitive representation]:
84    ///
85    /// ```rust
86    /// # use rich_err::error_code::{ErrorCode, ToErrorCode};
87    /// #[derive(Clone, Copy)]
88    /// #[repr(u16)] // `ErrorCode` is two bytes wide.
89    /// pub enum MyError {
90    ///     Foo,
91    ///     Bar(bool),
92    ///     Baz {
93    ///         something: f32,
94    ///     }
95    /// }
96    ///
97    /// impl ToErrorCode for MyError {
98    ///     fn code(&self) -> ErrorCode {
99    ///         unsafe { *<*const _>::from(self).cast::<ErrorCode>() }
100    ///     }
101    /// }
102    /// ```
103    ///
104    /// This approach is compatible with the prefixes from before, but examples will be omitted for
105    /// the sake of brevity.
106    ///
107    /// For more details regarding discriminants and safety, see the documentation for
108    /// [`core::mem::discriminant`].
109    ///
110    /// [primitive representation]: https://doc.rust-lang.org/reference/type-layout.html#primitive-representations
111    fn code(&self) -> ErrorCode;
112}