gufo_common/
utils.rs

1/// Adds conversions `from` and `into` integer to enums
2///
3/// Takes an enum that must have a `#[repr()]` as first meta field and assigns a
4/// value to all enum variants.
5///
6/// ```
7/// # use gufo_common::utils::convertible_enum;
8/// convertible_enum!(
9///     #[repr(u8)]
10///     #[derive(Debug, PartialEq)]
11///     pub enum Test {
12///         Val1 = 1,
13///         Val2 = 2,
14///     }
15/// );
16/// let int: u8 = Test::Val2.into();
17/// assert_eq!(int, 2);
18/// assert_eq!(Test::from(2), Test::Val2);
19/// assert_eq!(Test::from(3), Test::Unknown(3));
20/// ```
21#[macro_export]
22macro_rules! convertible_enum {
23    (#[repr($type:ty)]$(#[$meta:meta])* $visibility:vis enum $enum_name:ident {
24        $($(#[$variant_meta:meta])* $variant_name:ident = $variant_value:expr,)*
25    }) => {
26        #[repr($type)]
27        $(#[$meta])*
28        $visibility enum $enum_name {
29            $($(#[$variant_meta])* $variant_name = $variant_value,)*
30            Unknown($type)
31        }
32
33        impl std::convert::From<$type> for $enum_name {
34            fn from(v: $type) -> Self {
35                if false { unreachable!() }
36                $( else if v == $variant_value { Self::$variant_name } )*
37                else { Self::Unknown(v) }
38            }
39        }
40
41        impl std::convert::From<$enum_name> for $type {
42            fn from(value: $enum_name) -> $type {
43                match value {
44                    $($enum_name::$variant_name => $variant_value,)*
45                    $enum_name::Unknown(other) => other,
46                }
47            }
48        }
49    }
50}
51
52/// Adds conversions `try_from` and `into` integer to enums
53///
54/// Takes an enum that must have a `#[repr()]` as first meta field and assigns a
55/// value to all enum variants.
56///
57/// ```
58/// # use gufo_common::utils::maybe_convertible_enum;
59/// maybe_convertible_enum!(
60///     #[repr(u8)]
61///     #[derive(Debug, PartialEq)]
62///     pub enum Test {
63///         Val1 = 1,
64///         Val2 = 2,
65///     }
66/// );
67/// let int: u8 = Test::Val2.into();
68/// assert_eq!(int, 2);
69/// assert_eq!(Test::try_from(2), Ok(Test::Val2));
70/// assert_eq!(Test::try_from(3), Err(UnknownTestValueError(3)));
71/// ```
72#[macro_export]
73macro_rules! maybe_convertible_enum {
74    (#[repr($type:ty)]$(#[$meta:meta])* $visibility:vis enum $enum_name:ident {
75        $($(#[$variant_meta:meta])* $variant_name:ident = $variant_value:expr,)*
76    }) => {
77        #[repr($type)]
78        $(#[$meta])*
79        $visibility enum $enum_name {
80            $($(#[$variant_meta])* $variant_name = $variant_value,)*
81        }
82
83        paste::paste! {
84            #[derive(Debug, PartialEq, Eq)]
85            pub struct [<Unknown $enum_name ValueError>](pub $type);
86
87            impl std::fmt::Display for [<Unknown $enum_name ValueError>] {
88                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89                    write!(f, concat!("Enum '", stringify!($enum_name), "' has no variant with value '{}'"), self.0)
90                }
91            }
92
93            impl std::error::Error for [<Unknown $enum_name ValueError>] {}
94
95            /// Create enum from it's discriminant value
96            impl std::convert::TryFrom<$type> for $enum_name {
97                type Error =  [<Unknown $enum_name ValueError>];
98                fn try_from(v: $type) -> Result<Self, Self::Error> {
99                    match v {
100                        $($variant_value => Ok(Self::$variant_name),)*
101                        other => Err([<Unknown $enum_name ValueError>](other)),
102                    }
103                }
104            }
105        }
106
107        /// Convert enum to it's discriminant value
108        impl From<$enum_name> for $type {
109            fn from(v: $enum_name) -> $type {
110                match v {
111                    $($enum_name::$variant_name => $variant_value,)*
112                }
113            }
114        }
115    }
116}
117
118pub use {convertible_enum, maybe_convertible_enum};