1#[macro_export]
36macro_rules! define_header_enum {
37 (
38 $(tests_mod: $tests_mod:ident,)?
39 error_type: $Err:ident $(=> $err_msg:literal)?,
40 $(#[$enum_meta:meta])*
41 $vis:vis enum $Name:ident {
42 $(
43 $(#[$var_meta:meta])*
44 $variant:ident => $wire:literal
45 ),+ $(,)?
46 }
47 ) => {
48 $(
49 #[doc = concat!("Error for an unrecognized value; displays as `", $err_msg, " (<n> bytes)`. The rejected input is the field.")]
50 #[derive(Debug, Clone, PartialEq, Eq)]
51 $vis struct $Err(pub String);
52
53 impl std::fmt::Display for $Err {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 write!(f, concat!($err_msg, " ({} bytes)"), self.0.len())
56 }
57 }
58
59 impl std::error::Error for $Err {}
60 )?
61
62 $(#[$enum_meta])*
63 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
64 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
65 #[non_exhaustive]
66 #[allow(missing_docs)]
67 $vis enum $Name {
68 $(
69 $(#[$var_meta])*
70 $variant,
71 )+
72 }
73
74 impl $Name {
75 #[allow(unused_doc_comments)]
80 pub const ALL: &'static [Self] = &[
81 $(
82 $(#[$var_meta])*
83 $Name::$variant,
84 )+
85 ];
86
87 #[allow(unused_doc_comments)]
89 pub fn as_str(&self) -> &'static str {
90 match self {
91 $(
92 $(#[$var_meta])*
93 $Name::$variant => $wire,
94 )+
95 }
96 }
97 }
98
99 impl std::fmt::Display for $Name {
100 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101 f.write_str(self.as_str())
102 }
103 }
104
105 impl AsRef<str> for $Name {
106 fn as_ref(&self) -> &str {
107 self.as_str()
108 }
109 }
110
111 impl std::str::FromStr for $Name {
112 type Err = $Err;
113
114 #[allow(unused_doc_comments)]
115 fn from_str(s: &str) -> Result<Self, Self::Err> {
116 $(
117 $(#[$var_meta])*
118 if s.eq_ignore_ascii_case($wire) {
119 return Ok($Name::$variant);
120 }
121 )+
122 Err($Err(s.to_string()))
123 }
124 }
125
126 $(
127 #[cfg(test)]
128 mod $tests_mod {
129 use super::{$Err, $Name};
130
131 #[test]
132 fn round_trip() {
133 for v in $Name::ALL {
134 assert_eq!(v.as_str().parse::<$Name>(), Ok(*v));
135 }
136 }
137
138 #[test]
139 fn case_insensitive() {
140 for v in $Name::ALL {
141 assert_eq!(v.as_str().to_lowercase().parse::<$Name>(), Ok(*v));
142 assert_eq!(v.as_str().to_uppercase().parse::<$Name>(), Ok(*v));
143 }
144 }
145
146 #[test]
147 fn display_matches_as_str() {
148 for v in $Name::ALL {
149 assert_eq!(v.to_string(), v.as_str());
150 }
151 }
152
153 #[test]
154 fn unknown_input_err() {
155 let input = "\u{0}no-such-value\u{0}";
156 assert_eq!(input.parse::<$Name>(), Err($Err(input.to_string())));
157 }
158 }
159 )?
160 };
161}
162
163macro_rules! impl_from_str_via_parse {
165 ($Type:ty, $Err:ty) => {
166 impl std::str::FromStr for $Type {
167 type Err = $Err;
168
169 fn from_str(s: &str) -> Result<Self, Self::Err> {
170 Self::parse(s)
171 }
172 }
173 };
174}
175
176#[cfg(test)]
177mod tests {
178 define_header_enum! {
179 tests_mod: test_enum_generated,
180 error_type: ParseTestEnumError => "unknown test value",
181 pub(crate) enum TestEnum {
183 Foo => "Foo-Wire",
185 Bar => "Bar-Wire",
187 #[cfg(feature = "draft")]
189 Draft => "Draft-Wire",
190 }
191 }
192
193 #[derive(Debug, Clone, PartialEq, Eq)]
195 pub(crate) struct ParseOldEnumError(pub String);
196
197 impl std::fmt::Display for ParseOldEnumError {
198 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199 write!(f, "unknown old value: {}", self.0)
200 }
201 }
202
203 impl std::error::Error for ParseOldEnumError {}
204
205 define_header_enum! {
206 error_type: ParseOldEnumError,
207 pub(crate) enum OldEnum {
209 One => "Old-Wire",
211 }
212 }
213
214 #[test]
215 fn generated_error_display() {
216 let e = ParseTestEnumError("nope".to_string());
217 assert_eq!(e.to_string(), "unknown test value (4 bytes)");
218 }
219
220 #[test]
221 fn all_const_respects_cfg() {
222 #[cfg(not(feature = "draft"))]
223 assert_eq!(TestEnum::ALL, &[TestEnum::Foo, TestEnum::Bar]);
224 #[cfg(feature = "draft")]
225 assert_eq!(
226 TestEnum::ALL,
227 &[TestEnum::Foo, TestEnum::Bar, TestEnum::Draft]
228 );
229 }
230
231 #[test]
232 fn old_form_generates_all() {
233 assert_eq!(OldEnum::ALL, &[OldEnum::One]);
234 assert_eq!("old-wire".parse::<OldEnum>(), Ok(OldEnum::One));
235 }
236}