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}