1use std::fmt;
4
5use crate::class::{Class, Classify};
6
7#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct UnknownVariant {
12 pub type_name: &'static str,
14 pub value: String,
16}
17
18impl fmt::Display for UnknownVariant {
19 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20 write!(f, "{:?} is not a valid {}", self.value, self.type_name)
21 }
22}
23
24impl std::error::Error for UnknownVariant {}
25
26impl Classify for UnknownVariant {
30 fn class(&self) -> Class {
31 Class::InvalidRequest
32 }
33}
34
35#[macro_export]
59macro_rules! wire_enum {
60 (
61 $(#[$meta:meta])*
62 $vis:vis enum $name:ident {
63 $( $(#[$vmeta:meta])* $variant:ident => $wire:literal ),+ $(,)?
64 }
65 ) => {
66 $(#[$meta])*
67 #[derive(
68 Debug, Clone, Copy, PartialEq, Eq, Hash, ::serde::Serialize, ::serde::Deserialize,
69 )]
70 $vis enum $name {
71 $( $(#[$vmeta])* #[serde(rename = $wire)] $variant, )+
72 }
73
74 impl $name {
75 pub const ALL: &'static [Self] = &[$( Self::$variant ),+];
77
78 pub fn as_str(self) -> &'static str {
80 match self {
81 $( Self::$variant => $wire, )+
82 }
83 }
84 }
85
86 impl ::core::fmt::Display for $name {
87 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
88 f.write_str(self.as_str())
89 }
90 }
91
92 impl ::core::str::FromStr for $name {
93 type Err = $crate::UnknownVariant;
94
95 fn from_str(s: &str) -> ::core::result::Result<Self, Self::Err> {
96 match s {
97 $( $wire => ::core::result::Result::Ok(Self::$variant), )+
98 _ => ::core::result::Result::Err($crate::UnknownVariant {
99 type_name: ::core::stringify!($name),
100 value: ::std::borrow::ToOwned::to_owned(s),
101 }),
102 }
103 }
104 }
105 };
106}
107
108#[macro_export]
140macro_rules! open_enum {
141 (
142 $(#[$meta:meta])*
143 $vis:vis enum $name:ident {
144 $( $(#[$vmeta:meta])* $variant:ident => $wire:literal ),+ $(,)?
145 }
146 ) => {
147 $(#[$meta])*
148 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
149 #[non_exhaustive]
150 $vis enum $name {
151 $( $(#[$vmeta])* $variant, )+
152 Other(::std::string::String),
159 }
160
161 impl $name {
162 pub const ALL: &'static [Self] = &[$( Self::$variant ),+];
165
166 pub fn as_str(&self) -> &str {
168 match self {
169 $( Self::$variant => $wire, )+
170 Self::Other(raw) => raw,
171 }
172 }
173
174 pub fn from_wire(s: &str) -> Self {
176 match s {
177 $( $wire => Self::$variant, )+
178 other => Self::Other(::std::borrow::ToOwned::to_owned(other)),
179 }
180 }
181 }
182
183 impl ::core::fmt::Display for $name {
184 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
185 f.write_str(self.as_str())
186 }
187 }
188
189 impl ::core::str::FromStr for $name {
190 type Err = ::core::convert::Infallible;
191
192 fn from_str(s: &str) -> ::core::result::Result<Self, Self::Err> {
193 ::core::result::Result::Ok(Self::from_wire(s))
194 }
195 }
196
197 impl ::serde::Serialize for $name {
198 fn serialize<S: ::serde::Serializer>(
199 &self,
200 serializer: S,
201 ) -> ::core::result::Result<S::Ok, S::Error> {
202 serializer.serialize_str(self.as_str())
203 }
204 }
205
206 impl<'de> ::serde::Deserialize<'de> for $name {
207 fn deserialize<D: ::serde::Deserializer<'de>>(
208 deserializer: D,
209 ) -> ::core::result::Result<Self, D::Error> {
210 let raw = <::std::string::String as ::serde::Deserialize>::deserialize(deserializer)?;
211 ::core::result::Result::Ok(match raw.as_str() {
212 $( $wire => Self::$variant, )+
213 _ => Self::Other(raw),
214 })
215 }
216 }
217 };
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223
224 crate::wire_enum! {
225 #[non_exhaustive]
227 pub enum Interval {
228 M1 => "1m",
230 Mo1 => "1M",
232 }
233 }
234
235 crate::open_enum! {
236 pub enum State {
238 New => "STATE_NEW",
240 Confirmed => "STATE_CONFIRMED",
242 }
243 }
244
245 #[test]
246 fn an_unknown_variant_names_the_type_and_the_value() {
247 let err = UnknownVariant {
248 type_name: "Interval",
249 value: "2m".into(),
250 };
251 assert_eq!(err.to_string(), r#""2m" is not a valid Interval"#);
252 let boxed: Box<dyn std::error::Error> = Box::new(err);
253 assert!(boxed.source().is_none());
254 }
255
256 #[test]
257 fn an_unknown_variant_is_an_invalid_request() {
258 let err = "2m".parse::<Interval>().unwrap_err();
259 assert_eq!(err.class(), Class::InvalidRequest);
260 assert!(err.is_fault());
261 assert!(!err.is_retriable());
262 assert_eq!(Classify::retry_after(&err), None);
263 }
264
265 #[test]
266 fn a_wire_enum_round_trips_and_refuses_an_unknown_spelling() {
267 assert_eq!(Interval::ALL, &[Interval::M1, Interval::Mo1]);
268 for &variant in Interval::ALL {
269 let wire = variant.as_str();
270 assert_eq!(variant.to_string(), wire);
271 assert_eq!(wire.parse::<Interval>(), Ok(variant));
272 let json = serde_json::to_string(&variant).unwrap();
273 assert_eq!(json, format!("{wire:?}"));
274 assert_eq!(serde_json::from_str::<Interval>(&json).unwrap(), variant);
275 }
276 assert_eq!(
277 "2m".parse::<Interval>(),
278 Err(UnknownVariant {
279 type_name: "Interval",
280 value: "2m".into()
281 })
282 );
283 assert!(serde_json::from_str::<Interval>(r#""2m""#).is_err());
284 }
285
286 #[test]
287 fn an_open_enum_keeps_an_unknown_value_verbatim() {
288 let queued: State = serde_json::from_str(r#""STATE_QUEUED""#).unwrap();
289 assert_eq!(queued, State::Other("STATE_QUEUED".into()));
290 assert_eq!(queued.as_str(), "STATE_QUEUED");
291 assert_eq!(queued.to_string(), "STATE_QUEUED");
292 assert_eq!(serde_json::to_string(&queued).unwrap(), r#""STATE_QUEUED""#);
293
294 let known: State = serde_json::from_str(r#""STATE_NEW""#).unwrap();
295 assert_eq!(known, State::New);
296 assert_eq!(State::ALL, &[State::New, State::Confirmed]);
297 }
298
299 #[test]
300 fn from_wire_and_from_str_agree() {
301 for wire in ["STATE_NEW", "STATE_CONFIRMED", "STATE_QUEUED", ""] {
302 let Ok(parsed) = wire.parse::<State>();
303 assert_eq!(State::from_wire(wire), parsed, "{wire:?}");
304 assert_eq!(parsed.as_str(), wire);
305 }
306 }
307}