1
1
//! `caret`: Integers with some named values.
2
//!
3
//! # Crikey! Another Rust Enum Tool?
4
//!
5
//! Suppose you have an integer type with some named values.  For
6
//! example, you might be implementing a protocol where "command" can
7
//! be any 8-bit value, but where only a small number of commands are
8
//! recognized.
9
//!
10
//! In that case, you can use the [`caret_int`] macro to define a
11
//! wrapper around `u8` so named values are displayed with their
12
//! preferred format, but you can still represent all the other values
13
//! of the field:
14
//!
15
//! ```
16
//! use caret::caret_int;
17
//! caret_int!{
18
//!     struct Command(u8) {
19
//!        Get = 0,
20
//!        Put = 1,
21
//!        Swap = 2,
22
//!     }
23
//! }
24
//!
25
//! let c1: Command = 2.into();
26
//! let c2: Command = 100.into();
27
//!
28
//! assert_eq!(c1.to_string().as_str(), "Swap");
29
//! assert_eq!(c2.to_string().as_str(), "100");
30
//!
31
//! assert_eq!(c1, Command::Swap);
32
//! ```
33
//!
34
//! This crate is developed as part of
35
//! [Arti](https://gitlab.torproject.org/tpo/core/arti/), a project to
36
//! implement [Tor](https://www.torproject.org/) in Rust.
37
//! Many other crates in Arti depend on it, but it should be of general
38
//! use.
39

            
40
#![deny(missing_docs)]
41
#![warn(noop_method_call)]
42
#![deny(unreachable_pub)]
43
#![warn(clippy::all)]
44
#![deny(clippy::await_holding_lock)]
45
#![deny(clippy::cargo_common_metadata)]
46
#![deny(clippy::cast_lossless)]
47
#![deny(clippy::checked_conversions)]
48
#![warn(clippy::cognitive_complexity)]
49
#![deny(clippy::debug_assert_with_mut_call)]
50
#![deny(clippy::exhaustive_enums)]
51
#![deny(clippy::exhaustive_structs)]
52
#![deny(clippy::expl_impl_clone_on_copy)]
53
#![deny(clippy::fallible_impl_from)]
54
#![deny(clippy::implicit_clone)]
55
#![deny(clippy::large_stack_arrays)]
56
#![warn(clippy::manual_ok_or)]
57
#![deny(clippy::missing_docs_in_private_items)]
58
#![deny(clippy::missing_panics_doc)]
59
#![warn(clippy::needless_borrow)]
60
#![warn(clippy::needless_pass_by_value)]
61
#![warn(clippy::option_option)]
62
#![warn(clippy::rc_buffer)]
63
#![deny(clippy::ref_option_ref)]
64
#![warn(clippy::semicolon_if_nothing_returned)]
65
#![warn(clippy::trait_duplication_in_bounds)]
66
#![deny(clippy::unnecessary_wraps)]
67
#![warn(clippy::unseparated_literal_suffix)]
68
#![deny(clippy::unwrap_used)]
69

            
70
/// Declare an integer type with some named elements.
71
///
72
/// This macro declares a struct that wraps an integer
73
/// type, and allows any integer type as a value.  Some values of this type
74
/// have names, and others do not, but they are all allowed.
75
///
76
/// This macro is suitable for protocol implementations that accept
77
/// any integer on the wire, and have definitions for some of those
78
/// integers.  For example, Tor cell commands are 8-bit integers, but
79
/// not every u8 is a currently recognized Tor command.
80
///
81
/// # Examples
82
/// ```
83
/// use caret::caret_int;
84
/// caret_int! {
85
///     pub struct FruitID(u8) {
86
///         AVOCADO = 7,
87
///         PERSIMMON = 8,
88
///         LONGAN = 99
89
///     }
90
/// }
91
///
92
/// // Known fruits work the way we would expect...
93
/// let a_num: u8 = FruitID::AVOCADO.into();
94
/// assert_eq!(a_num, 7);
95
/// let a_fruit: FruitID = 8.into();
96
/// assert_eq!(a_fruit, FruitID::PERSIMMON);
97
/// assert_eq!(format!("I'd like a {}", FruitID::PERSIMMON),
98
///            "I'd like a PERSIMMON");
99
///
100
/// // And we can construct unknown fruits, if we encounter any.
101
/// let weird_fruit: FruitID = 202.into();
102
/// assert_eq!(format!("I'd like a {}", weird_fruit),
103
///            "I'd like a 202");
104
/// ```
105
#[macro_export]
106
macro_rules! caret_int {
107
    {
108
       $(#[$meta:meta])*
109
       $v:vis struct $name:ident ( $numtype:ty ) {
110
           $(
111
               $(#[$item_meta:meta])*
112
               $id:ident = $num:literal
113
           ),*
114
           $(,)?
115
      }
116
    } => {
117
3905
        #[derive(PartialEq,Eq,Copy,Clone)]
118
        $(#[$meta])*
119
        $v struct $name($numtype);
120

            
121
        impl From<$name> for $numtype {
122
1429072
            fn from(val: $name) -> $numtype { val.0 }
123
        }
124
        impl From<$numtype> for $name {
125
60409
            fn from(num: $numtype) -> $name { $name(num) }
126
        }
127
        impl $name {
128
            $(
129
                $( #[$item_meta] )*
130
                pub const $id: $name = $name($num) ; )*
131
1567
            fn to_str(self) -> Option<&'static str> {
132
1567
                match self {
133
27
                    $( $name::$id => Some(stringify!($id)), )*
134
223
                    _ => None,
135
                }
136
1567
            }
137
            /// Return true if this value is one that we recognize.
138
2
            $v fn is_recognized(self) -> bool {
139
2
                matches!(self,
140
                         $( $name::$id )|*)
141
2
            }
142
            /// Try to convert this value from one of the recognized names.
143
61315
            $v fn from_name(name: &str) -> Option<Self> {
144
61315
                match name {
145
61315
                    $( stringify!($id) => Some($name::$id), )*
146
3287
                    _ => None
147
                }
148
61665
            }
149
            /// Return the underlying integer that this value represents.
150
1368158
            fn get(self) -> $numtype {
151
1368158
                self.into()
152
1368158
            }
153
        }
154
        impl std::fmt::Display for $name {
155
1564
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156
1564
                match self.to_str() {
157
1342
                    Some(s) => write!(f, "{}", s),
158
222
                    None => write!(f, "{}", self.0),
159
                }
160
1564
            }
161
        }
162
        // `#[educe(Debug)]` could do this for us, but let's not deepen this macrology
163
        impl std::fmt::Debug for $name {
164
618
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165
618
                write!(f, "{}({})", stringify!($name), self)
166
618
            }
167
        }
168
    };
169

            
170
}