beerec_variants/lib.rs
1mod ident;
2mod nested_meta;
3mod rename;
4mod string;
5mod target;
6
7use darling::FromDeriveInput;
8use proc_macro::TokenStream;
9use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
10use syn::DeriveInput;
11
12use self::target::r#enum::TargetEnum;
13
14/// The actual derive macro implementation.
15///
16/// This function is introduced in conjunction with `derive_enum_variants`
17/// because the `proc_macro_derive` attribute requires its target function
18/// signature to be `fn(proc_macro::TokenStream) -> proc_macro::TokenStream`.
19///
20/// This `impl` function allows to return a `syn::Result`, enabling the `?`
21/// operator for fallible function calls. This way, the macro's function
22/// can map the error value to a compilation error, providing more precise
23/// error messages.
24#[rustfmt::skip]
25#[allow(clippy::too_many_lines)]
26fn derive_enum_variants_impl(input: &DeriveInput) -> syn::Result<TokenStream2> {
27 let target_enum = TargetEnum::from_derive_input(input)?;
28
29 let enum_ident = target_enum.ident();
30
31 let variants_count = target_enum.variants_count();
32 let variants_idents = target_enum.iter_variant_idents();
33
34 let variants_as_str_match_branches = target_enum.iter_variant_as_str_match_branches();
35 let variants_as_str_abbr_match_branches = target_enum.iter_variant_as_str_abbr_match_branches();
36
37 let variants_list_string = target_enum.variants_list_string();
38 let variants_list_string_abbr = target_enum.variants_list_string_abbr();
39
40 let iterable_variants_doc = format!(
41 "The array of _iterable_ (i.e. non-skipped) [`{enum_ident}`] variants."
42 );
43
44 let iterable_variants_count_doc = format!(
45 "The number of _iterable_ (i.e. non-skipped) [`{enum_ident}`] variants."
46 );
47
48 let as_str_doc = format!(
49 r"Returns a string representation of the [`{enum_ident}`] variant.
50
51# Notes
52
53This method is generated by the [`Variants`] derive macro from the [`beerec-variants`]
54crate, it applies rename strategies following a priority-based fallback approach:
55
561. [`InnerRenameStrategy`] (_highest priority_) - uses the string
57 produced by the rename strategy from the `#[variants(rename(...))]`
58 attribute, if one has been specified for the variant;
591. [`OuterRenameStrategy`] (_fallback_) - uses the string produced by the
60 rename strategy from the `#[variants(rename(...))]` attribute, if one has
61 been specified for the type;
621. **No renaming** (_default_) - converts the variant identifier to a string
63 if neither the type-level nor the variant-level rename attribute has been
64 specified.
65
66[`beerec-variants`]: https://docs.rs/beerec-variants
67[`Variants`]: https://docs.rs/beerec-variants/latest/beerec_variants/derive.Variants.html"
68 );
69
70 let as_str_abbr_doc = format!(
71 r"Returns an abbreviated string representation of the [`{enum_ident}`] variant.
72
73# Notes
74
75This method is generated by the [`Variants`] derive macro from the [`beerec-variants`]
76crate, it applies rename strategies on the string representation of the
77variant, following a priority-based fallback approach:
78
791. [`InnerRenameStrategy`] (_highest priority_) - uses the abbreviated
80 string produced by the rename strategy from the `#[variants(rename_abbr(...))]`
81 attribute, if one has been specified for the variant;
821. [`OuterRenameStrategy`] (_fallback_) - uses the abbreviated string produced
83 by the rename strategy from the `#[variants(rename_abbr(...))]` attribute, if
84 one has been specified for the type;
851. **No renaming** (_default_) - abbreviates the full length string representation
86 of the variant as is, without applying any renaming strategy.
87
88Likewise, the renaming follows a priority-based fallback approach to
89determine the full length string representation before applying the
90abbreviation:
91
921. **Variant-level attribute** (_highest priority_) - uses the string
93 produced by the rename strategy from the `#[variants(rename(...))]`
94 attribute, if one has been specified for the type;
951. **Type-level attribute** (_fallback_) - uses the string produced by the
96 rename strategy from the `#[variants(rename(...))]` attribute, if one has
97 been specified for the type;
981. **No renaming** (_default_) - converts the variant identifier to a string
99 if neither the type-level nor the variant-level rename attribute has been
100 specified.
101
102[`beerec-variants`]: https://docs.rs/beerec-variants
103[`Variants`]: https://docs.rs/beerec-variants/latest/beerec_variants/derive.Variants.html"
104 );
105
106 let iter_variants_doc = format!(
107 r"Iterates over _iterable_ (i.e. non-skipped) [`{enum_ident}`] variants.
108
109# Notes
110
111This method is generated by the [`Variants`] derive macro from the [`beerec-variants`] crate,
112enum variants marked with the `#[variants(skip)]` attribute are excluded from iteration.
113
114[`beerec-variants`]: https://docs.rs/beerec-variants
115[`Variants`]: https://docs.rs/beerec-variants/latest/beerec_variants/derive.Variants.html"
116 );
117
118 let iter_variants_as_str_doc = format!(
119 r"Iterates over _iterable_ (i.e. non-skipped) string representations of [`{enum_ident}`]
120variants.
121
122See [`{enum_ident}::as_str`] for further details about yielded values.
123
124# Notes
125
126This method is generated by the [`Variants`] derive macro from the [`beerec-variants`] crate,
127enum variants marked with the `#[variants(skip)]` attribute are excluded from iteration.
128
129[`beerec-variants`]: https://docs.rs/beerec-variants
130[`Variants`]: https://docs.rs/beerec-variants/latest/beerec_variants/derive.Variants.html"
131 );
132
133 let iter_variants_as_str_abbr_doc = format!(
134 r"Iterates over _iterable_ (i.e. non-skipped) abbreviated string representations of
135[`{enum_ident}`] variants.
136
137See [`{enum_ident}::as_str_abbr`] for further details about yielded values.
138
139# Notes
140
141This method is generated by the [`Variants`] derive macro from the [`beerec-variants`] crate,
142enum variants marked with the `#[variants(skip)]` attribute are excluded from iteration.
143
144[`beerec-variants`]: https://docs.rs/beerec-variants
145[`Variants`]: https://docs.rs/beerec-variants/latest/beerec_variants/derive.Variants.html"
146 );
147
148 let variants_list_str_doc = format!(
149 r"Returns a list of quoted (double-quotes) and comma separated string
150representations of _iterable_ (i.e. non-skipped) [`{enum_ident}`] variants.
151
152See [`{enum_ident}::as_str`] for further details about the string representations.
153
154# Notes
155
156This method is generated by the [`Variants`] derive macro from the [`beerec-variants`] crate,
157enum variants marked with the `#[variants(skip)]` attribute are excluded from the listing.
158
159[`beerec-variants`]: https://docs.rs/beerec-variants
160[`Variants`]: https://docs.rs/beerec-variants/latest/beerec_variants/derive.Variants.html"
161 );
162
163 let variants_list_str_abbr_doc = format!(
164 r"Returns a list of quoted (double-quotes) and comma separated abbreviated string
165representations of _iterable_ (i.e. non-skipped) [`{enum_ident}`] variants.
166
167See [`{enum_ident}::as_str_abbr`] for further details about the abbreviated string representations.
168
169# Notes
170
171This method is generated by the [`Variants`] derive macro from the [`beerec-variants`] crate,
172enum variants marked with the `#[variants(skip)]` attribute are excluded from the listing.
173
174[`beerec-variants`]: https://docs.rs/beerec-variants
175[`Variants`]: https://docs.rs/beerec-variants/latest/beerec_variants/derive.Variants.html"
176 );
177
178 let mut generated = quote::quote! {
179 impl ::std::marker::Copy for #enum_ident {}
180
181 impl ::std::clone::Clone for #enum_ident {
182 fn clone(&self) -> Self {
183 *self
184 }
185 }
186
187 #[automatically_derived]
188 impl #enum_ident {
189 #[doc = #iterable_variants_doc]
190 const ITERABLE_VARIANTS: [Self; #variants_count] = [
191 #(Self::#variants_idents,)*
192 ];
193
194 #[doc = #iterable_variants_count_doc]
195 const ITERABLE_VARIANTS_COUNT: usize = #variants_count;
196
197 #[inline]
198 #[must_use]
199 #[doc = #as_str_doc]
200 pub const fn as_str(self) -> &'static str {
201 match self {
202 #(#variants_as_str_match_branches,)*
203 }
204 }
205
206 #[inline]
207 #[must_use]
208 #[doc = #as_str_abbr_doc]
209 pub const fn as_str_abbr(self) -> &'static str {
210 match self {
211 #(#variants_as_str_abbr_match_branches,)*
212 }
213 }
214
215 #[doc = #iter_variants_doc]
216 pub fn iter_variants() -> impl ::std::iter::Iterator<Item = Self> {
217 Self::ITERABLE_VARIANTS.into_iter()
218 }
219
220 #[doc = #iter_variants_as_str_doc]
221 pub fn iter_variants_as_str() -> impl ::std::iter::Iterator<Item = &'static str> {
222 Self::iter_variants().map(Self::as_str)
223 }
224
225 #[doc = #iter_variants_as_str_abbr_doc]
226 pub fn iter_variants_as_str_abbr() -> impl ::std::iter::Iterator<Item = &'static str> {
227 Self::iter_variants().map(Self::as_str_abbr)
228 }
229
230 #[doc = #variants_list_str_doc]
231 pub const fn variants_list_str() -> &'static str {
232 #variants_list_string
233 }
234
235 #[doc = #variants_list_str_abbr_doc]
236 pub const fn variants_list_str_abbr() -> &'static str {
237 #variants_list_string_abbr
238 }
239 }
240 };
241
242 if target_enum.implement_display() {
243 let generated_display_impl = quote::quote! {
244 impl ::std::fmt::Display for #enum_ident {
245 fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
246 ::std::fmt::Formatter::write_str(f, self.as_str())
247 }
248 }
249 };
250
251 generated.extend(generated_display_impl);
252 }
253
254 if target_enum.implement_from_str() {
255 let parse_error_ident = Ident::new(&format!("Parse{enum_ident}Error"), Span::call_site());
256 let variants_from_str_match_branches = target_enum.variants_from_str_match_branches();
257
258 let generated_from_str_impl = quote::quote! {
259 #[derive(Debug, PartialEq, Eq)]
260 pub struct #parse_error_ident;
261
262 impl ::std::fmt::Display for #parse_error_ident {
263 fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
264 ::std::fmt::Formatter::write_str(f, "Expected one of ")?;
265 ::std::fmt::Formatter::write_str(f, #enum_ident::variants_list_str())?;
266 ::std::fmt::Formatter::write_str(f, " or one of ")?;
267 ::std::fmt::Formatter::write_str(f, #enum_ident::variants_list_str_abbr())?;
268
269 Ok(())
270 }
271 }
272
273 impl ::std::error::Error for #parse_error_ident {}
274
275 impl ::std::str::FromStr for #enum_ident {
276 type Err = #parse_error_ident;
277
278 fn from_str(value: &str) -> ::std::result::Result<Self, Self::Err> {
279 match value {
280 #(#variants_from_str_match_branches,)*
281 _ => ::std::result::Result::Err(#parse_error_ident),
282 }
283 }
284 }
285 };
286
287 generated.extend(generated_from_str_impl);
288 }
289
290 #[cfg(feature = "serde")]
291 if target_enum.implement_deserialize() {
292 let visitor_ident = Ident::new(&format!("{enum_ident}Visitor"), Span::call_site());
293 let variants_deserialize_match_branches = target_enum.variants_deserialize_match_branches();
294
295 let generated_deserialize_impl = quote::quote! {
296 impl<'de> ::serde::de::Deserialize<'de> for #enum_ident {
297 fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
298 where
299 D: ::serde::de::Deserializer<'de>
300 {
301 struct #visitor_ident;
302
303 impl<'de> ::serde::de::Visitor<'de> for #visitor_ident {
304 type Value = #enum_ident;
305
306 fn expecting(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
307 ::std::fmt::Formatter::write_str(f, "one of ")?;
308 ::std::fmt::Formatter::write_str(f, #enum_ident::variants_list_str())?;
309 ::std::fmt::Formatter::write_str(f, " or one of ")?;
310 ::std::fmt::Formatter::write_str(f, #enum_ident::variants_list_str_abbr())?;
311
312 Ok(())
313 }
314
315 fn visit_str<E>(self, value: &str) -> ::std::result::Result<Self::Value, E>
316 where
317 E: ::serde::de::Error,
318 {
319 match value {
320 #(#variants_deserialize_match_branches,)*
321 _ => {
322 let unexp = ::serde::de::Unexpected::Str(value);
323 let error = ::serde::de::Error::invalid_value(unexp, &self);
324 ::std::result::Result::Err(error)
325 },
326 }
327 }
328 }
329
330 deserializer.deserialize_str(#visitor_ident)
331 }
332 }
333 };
334
335 generated.extend(generated_deserialize_impl);
336 }
337
338 #[cfg(feature = "serde")]
339 if target_enum.implement_serialize() {
340 let generated_serialize_impl = quote::quote! {
341 impl ::serde::ser::Serialize for #enum_ident {
342 fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
343 where
344 S: ::serde::ser::Serializer,
345 {
346 serializer.serialize_str(self.as_str())
347 }
348 }
349 };
350
351 generated.extend(generated_serialize_impl);
352 }
353
354 Ok(generated)
355}
356
357/// Derive macro to generate boilerplate on unit variants `enum` types.
358///
359/// The macro generates the following methods:
360///
361/// - `as_str` - returns a string representation of the target `enum` variant;
362/// - `as_str_abbr` - returns an abbreviated string representation of the target
363/// `enum` variant;
364/// - `iter_variants` - returns an iterator over target `enum` variants (owned
365/// values);
366/// - `iter_variants_as_str` - returns an iterator over string representations
367/// of the target `enum` variants (`&'static str` values);
368/// - `iter_variants_as_str_abbr` - returns an iterator over abbreviated string
369/// representations of the `enum` variants (`&'static str` values);
370/// - `variants_list_str` - returns a list of quoted (double-quotes) and comma
371/// separated string representations of the `enum` variants;
372/// - `variants_list_str_abbr` - returns a list of of quoted (double-quotes) and
373/// comma separated abbreviated string representation of the `enum` variants.
374///
375/// # Enum level attributes
376///
377/// The macro exposes the following `enum` outer attributes (i.e. attributes to
378/// be applied to the `enum` type the macro is being derived on):
379///
380/// - `rename` - customizes the string representation of each variant;
381/// - `rename_abbr` - customizes the abbreviated string representation of each
382/// variant;
383/// - `display` - generates a [`Display`] trait implementation based on the
384/// string representation provided by the generated `as_str` method;
385/// - `from_str` - generates a [`FromStr`] trait implementation based on the
386/// string or abbreviated string representation provided by the generated
387/// `as_str` and `as_str_abbr` methods respectively.
388///
389/// Valid `rename` and `rename_abbr` customization strategies are:
390///
391/// - `uppercase` - makes the (abbreviated) string representation uppercase;
392/// - `lowercase` - makes the (abbreviated) string representation lowercase.
393///
394/// ## Examples
395///
396/// ```rust
397/// # use beerec_variants::Variants;
398/// #
399/// #[derive(Variants)]
400/// #[variants(rename(uppercase))]
401/// enum CardinalDirection {
402/// North,
403/// East,
404/// South,
405/// West,
406/// }
407///
408/// # fn main() {
409/// assert_eq!("NORTH", CardinalDirection::North.as_str());
410/// assert_eq!("EAST", CardinalDirection::East.as_str());
411/// assert_eq!("SOUTH", CardinalDirection::South.as_str());
412/// assert_eq!("WEST", CardinalDirection::West.as_str());
413///
414/// assert_eq!("NOR", CardinalDirection::North.as_str_abbr());
415/// assert_eq!("EAS", CardinalDirection::East.as_str_abbr());
416/// assert_eq!("SOU", CardinalDirection::South.as_str_abbr());
417/// assert_eq!("WES", CardinalDirection::West.as_str_abbr());
418/// # }
419/// ```
420///
421/// ```rust
422/// # use beerec_variants::Variants;
423/// #
424/// #[derive(Variants)]
425/// #[variants(rename(lowercase), rename_abbr(uppercase))]
426/// enum State {
427/// Active,
428/// Inactive,
429/// Disabled,
430/// }
431///
432/// # fn main() {
433/// assert_eq!("active", State::Active.as_str());
434/// assert_eq!("inactive", State::Inactive.as_str());
435/// assert_eq!("disabled", State::Disabled.as_str());
436///
437/// assert_eq!("ACT", State::Active.as_str_abbr());
438/// assert_eq!("INA", State::Inactive.as_str_abbr());
439/// assert_eq!("DIS", State::Disabled.as_str_abbr());
440/// # }
441/// ```
442///
443/// ```rust
444/// # use beerec_variants::Variants;
445/// #
446/// #[derive(Variants)]
447/// #[variants(display)]
448/// enum Season {
449/// Spring,
450/// Summer,
451/// Autumn,
452/// Winter,
453/// }
454///
455/// # fn main() {
456/// assert_eq!(String::from("Spring"), Season::Spring.to_string());
457/// assert_eq!(String::from("Summer"), Season::Summer.to_string());
458/// assert_eq!(String::from("Autumn"), Season::Autumn.to_string());
459/// assert_eq!(String::from("Winter"), Season::Winter.to_string());
460///
461/// assert_eq!(String::from("Spring"), format!("{}", Season::Spring));
462/// assert_eq!(String::from("Summer"), format!("{}", Season::Summer));
463/// assert_eq!(String::from("Autumn"), format!("{}", Season::Autumn));
464/// assert_eq!(String::from("Winter"), format!("{}", Season::Winter));
465/// # }
466/// ```
467///
468/// ```rust
469/// # use std::str::FromStr;
470/// #
471/// # use beerec_variants::Variants;
472/// #
473/// #[derive(Debug, Variants, PartialEq, Eq)]
474/// #[variants(from_str)]
475/// enum Priority {
476/// Low,
477/// Medium,
478/// High,
479/// Critical,
480/// }
481///
482/// # fn main() {
483/// assert_eq!(Ok(Priority::Low), <Priority as FromStr>::from_str("Low"));
484/// assert_eq!(Ok(Priority::Medium), <Priority as FromStr>::from_str("Medium"));
485/// assert_eq!(Ok(Priority::High), <Priority as FromStr>::from_str("High"));
486/// assert_eq!(Ok(Priority::Critical), <Priority as FromStr>::from_str("Critical"));
487///
488/// assert_eq!(Ok(Priority::Low), <Priority as FromStr>::from_str("Low"));
489/// assert_eq!(Ok(Priority::Medium), <Priority as FromStr>::from_str("Med"));
490/// assert_eq!(Ok(Priority::High), <Priority as FromStr>::from_str("Hig"));
491/// assert_eq!(Ok(Priority::Critical), <Priority as FromStr>::from_str("Cri"));
492///
493/// assert_eq!(Err(ParsePriorityError), <Priority as FromStr>::from_str("invalid"));
494/// # }
495/// ```
496///
497/// ## Feature-gated attributes
498///
499/// ### [Serde](https://crates.io/crates/serde)
500///
501/// The following `enum` outer attributes are exposed when the `serde` feature is
502/// enabled:
503///
504/// - `deserialize` - generates a [`Deserialize`] trait implementation based on
505/// the string or abbreviated string representation provided by the generated
506/// `as_str` and `as_str_abbr` respectively;
507/// - `serialize` - generates a [`Serialize`] trait implementation based on the
508/// string representation provided by the generated `as_str` method.
509///
510/// #### Examples
511///
512/// ```rust
513/// # use beerec_variants::Variants;
514/// #
515/// #[derive(Debug, Variants, PartialEq, Eq)]
516/// # #[cfg(feature = "serde")]
517/// #[variants(deserialize)]
518/// enum Theme {
519/// Auto,
520/// Dark,
521/// Light,
522/// }
523///
524/// #[derive(Debug, PartialEq, Eq)]
525/// # #[cfg(feature = "serde")]
526/// #[derive(serde::Deserialize)]
527/// struct Config {
528/// theme: Theme,
529/// }
530///
531/// # fn main() {
532/// # #[cfg(feature = "serde")]
533/// # {
534/// // Deserialize from variant string representation.
535/// assert_eq!(
536/// Ok(Config { theme: Theme::Auto }),
537/// toml::from_str::<'_, Config>("theme = \"Auto\"\n"),
538/// );
539///
540/// assert_eq!(
541/// Ok(Config { theme: Theme::Dark }),
542/// toml::from_str::<'_, Config>("theme = \"Dark\"\n"),
543/// );
544///
545/// assert_eq!(
546/// Ok(Config { theme: Theme::Light }),
547/// toml::from_str::<'_, Config>("theme = \"Light\"\n"),
548/// );
549///
550/// // Deserialize from variant abbreviated string representation.
551/// assert_eq!(
552/// Ok(Config { theme: Theme::Auto }),
553/// toml::from_str::<'_, Config>("theme = \"Aut\"\n"),
554/// );
555///
556/// assert_eq!(
557/// Ok(Config { theme: Theme::Dark }),
558/// toml::from_str::<'_, Config>("theme = \"Dar\"\n"),
559/// );
560///
561/// assert_eq!(
562/// Ok(Config { theme: Theme::Light }),
563/// toml::from_str::<'_, Config>("theme = \"Lig\"\n"),
564/// );
565/// # }
566/// # }
567/// ```
568///
569/// ```rust
570/// # use beerec_variants::Variants;
571/// #
572/// #[derive(Debug, Variants, PartialEq, Eq)]
573/// # #[cfg(feature = "serde")]
574/// #[variants(serialize)]
575/// enum Codec {
576/// H264,
577/// H265,
578/// AV1,
579/// }
580///
581/// #[derive(Debug, PartialEq, Eq)]
582/// # #[cfg(feature = "serde")]
583/// #[derive(serde::Serialize)]
584/// struct Config {
585/// codec: Codec,
586/// }
587///
588/// # fn main() {
589/// # #[cfg(feature = "serde")]
590/// # {
591/// assert_eq!(
592/// Ok(String::from("codec = \"H264\"\n")),
593/// toml::to_string(&Config { codec: Codec::H264 }),
594/// );
595///
596/// assert_eq!(
597/// Ok(String::from("codec = \"H265\"\n")),
598/// toml::to_string(&Config { codec: Codec::H265 }),
599/// );
600///
601/// assert_eq!(
602/// Ok(String::from("codec = \"AV1\"\n")),
603/// toml::to_string(&Config { codec: Codec::AV1 }),
604/// );
605/// # }
606/// # }
607/// ```
608///
609/// # Variant level attributes
610///
611/// The macro exposes the following variant attributes:
612///
613/// - `skip` - excludes the marked variant from iteration and listing;
614/// - `rename` - customizes the string representation of the marked variant;
615/// - `rename_abbr` - customizes the abbreviated string representation of the
616/// marked variant.
617///
618/// Valid `rename` and `rename_abbr` customization strategies are:
619///
620/// - `"..."` (string literal) - overrides the string representation with a
621/// custom string;
622/// - `uppercase` - makes the (abbreviated) string representation uppercase;
623/// - `lowercase` - makes the (abbreviated) string representation lowercase.
624///
625/// For custom string overrides:
626///
627/// - `#[variants(rename = "...")]` is equivalent to
628/// `#[variants(rename("..."))]`;
629/// - `#[variants(rename_abbr = "...")]` is equivalent to
630/// `#[variants(rename_abbr("..."))]`;
631///
632/// both are valid, supported formats.
633///
634/// ## Examples
635///
636/// ```rust
637/// # use beerec_variants::Variants;
638/// #
639/// #[derive(Variants)]
640/// enum Format {
641/// Xml,
642/// Csv,
643/// #[variants(rename("plain-text"), rename_abbr = "txt")]
644/// PlainText,
645/// }
646///
647/// # fn main() {
648/// assert_eq!("Xml", Format::Xml.as_str());
649/// assert_eq!("Csv", Format::Csv.as_str());
650/// assert_eq!("plain-text", Format::PlainText.as_str());
651///
652/// assert_eq!("Xml", Format::Xml.as_str_abbr());
653/// assert_eq!("Csv", Format::Csv.as_str_abbr());
654/// assert_eq!("txt", Format::PlainText.as_str_abbr());
655/// # }
656/// ```
657///
658/// # String representation renaming priority
659///
660/// To produce _string representations_ of enum variants, renaming can be
661/// applied at both the type level and variant level. The string representation
662/// of each variant is obtained by applying rename strategies following a
663/// priority-based fallback approach:
664///
665/// 1. **Variant-level attribute** (_highest priority_) - usese the string
666/// produced by the rename strategy from the `#[variants(rename(...))]`
667/// attribute, if one has been specified for the variant;
668/// 1. **Type-level attribute** (_fallback_) - uses the string produced by the
669/// rename strategy from the `#[variants(rename(...))]` attribute, if one has
670/// been specified for the type;
671/// 1. **No renaming** (_default_) - converts the variant identifier to a string
672/// if neither the type-level nor the variant-level rename attribute has been
673/// specified.
674///
675/// # Abbreviated string representation renaming priority
676///
677/// To produce _abbreviated string representations_ of the enum variants,
678/// renaming can be applied at both the type level and the variant level. The
679/// abbreviated string representation of each variant is obtained by applying
680/// rename strategies following a priority-based fallback approach:
681///
682/// 1. **Variant-level attribute** (_highest priority_) - uses the abbreviated
683/// string produced by the rename strategy from the
684/// `#[variants(rename_abbr(...))]` attribute, if one has been specified for
685/// the variant;
686/// 1. **Type-level attribute** (_fallback_) - uses the string produced by the
687/// rename strategy from the `#[variants(rename(...))]` attribute, if one has
688/// been specified for the type;
689/// 1. **No renaming** (_default_) - abbreviates the full length string
690/// representation of the variant as is, without applying any renaming
691/// strategy.
692///
693/// Likewise, the renaming follows a priority-based fallback approach to
694/// determine the full length string representation before applying the
695/// abbreviation:
696///
697/// 1. **Variant-level attribute** (_highest priority_) - uses the string
698/// produced by the rename strategy from the `#[variants(rename(...))]`
699/// attribute, if one has been specified for the type;
700/// 1. **Type-level attribute** (_fallback_) - uses the string produced by the
701/// rename strategy from the `#[variants(rename(...))]` attribute, if one has
702/// been specified for the type;
703/// 1. **No renaming** (_default_) - converts the variant identifier to a string
704/// if neither the type-level nor the variant-level rename attribute has been
705/// specified.
706///
707/// # Errors
708///
709/// The macro will produce a compile error if:
710///
711/// - derived on `struct` types;
712/// - derived on `union` types;
713/// - derived on `enum` types with any named field variants;
714/// - derived on `enum` types with any unnamed field (i.e. tuple) variants;
715/// - derived on `enum` types with any newtype variants;
716/// - the `rename` variant-level attribute is passed any other value than a
717/// string literal, `uppercase` or `lowercase`;
718/// - the `rename_abbr` variant-level attribute is passed any other value than a
719/// string literal, `uppercase` or `lowercase`;
720/// - the `rename` type-level attribute is passed any other value than
721/// `uppercase` or `lowercase`;
722/// - the `rename_abbr` type-level attribute is passed any other value than
723/// `uppercase` or `lowercase`.
724///
725/// # Notes
726///
727/// Deriving [`Variants`] on type automatically implements [`Clone`] and
728/// [`Copy`] for such type. This means that deriving either trait on a type that
729/// also derives [`Variants`] will result in a "conflicting implementations"
730/// compilation error.
731///
732/// # Examples
733///
734/// ```rust
735/// # use std::str::FromStr;
736/// #
737/// # use beerec_variants::Variants;
738/// #
739/// #[derive(Variants, Debug, PartialEq, Eq)]
740/// #[cfg_attr(feature = "serde", variants(deserialize, serialize))]
741/// #[variants(display, from_str)]
742/// enum Weekday {
743/// #[variants(skip)]
744/// Monday,
745/// #[variants(rename = "DayAfterMonday", rename_abbr = "tue")]
746/// Tuesday,
747/// #[variants(rename_abbr = "wed")]
748/// Wednesday,
749/// #[variants(rename = "Giovedì", rename_abbr(lowercase))]
750/// Thursday,
751/// Friday,
752/// Saturday,
753/// Sunday,
754/// }
755///
756/// #[derive(Debug, PartialEq, Eq)]
757/// #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
758/// struct Schedule {
759/// weekday: Weekday,
760/// }
761///
762/// # fn main() {
763/// // Monday has been marked as `skip`, iterator will yield 6 values.
764/// assert_eq!(6, Weekday::iter_variants().count());
765///
766/// assert_eq!("Monday", Weekday::Monday.as_str());
767/// assert_eq!("DayAfterMonday", Weekday::Tuesday.as_str());
768/// assert_eq!("Wednesday", Weekday::Wednesday.as_str());
769/// assert_eq!("Giovedì", Weekday::Thursday.as_str());
770/// assert_eq!("Friday", Weekday::Friday.as_str());
771/// assert_eq!("Saturday", Weekday::Saturday.as_str());
772/// assert_eq!("Sunday", Weekday::Sunday.as_str());
773///
774/// assert_eq!("Mon", Weekday::Monday.as_str_abbr());
775/// assert_eq!("tue", Weekday::Tuesday.as_str_abbr());
776/// assert_eq!("wed", Weekday::Wednesday.as_str_abbr());
777/// assert_eq!("gio", Weekday::Thursday.as_str_abbr());
778/// assert_eq!("Fri", Weekday::Friday.as_str_abbr());
779/// assert_eq!("Sat", Weekday::Saturday.as_str_abbr());
780/// assert_eq!("Sun", Weekday::Sunday.as_str_abbr());
781///
782/// // The enum has been marked as `display`, so `std::fmt::Display` implementation is available.
783/// assert_eq!(String::from("Monday"), Weekday::Monday.to_string());
784/// assert_eq!(String::from("DayAfterMonday"), Weekday::Tuesday.to_string());
785/// assert_eq!(String::from("Wednesday"), Weekday::Wednesday.to_string());
786/// assert_eq!(String::from("Giovedì"), Weekday::Thursday.to_string());
787/// assert_eq!(String::from("Friday"), Weekday::Friday.to_string());
788/// assert_eq!(String::from("Saturday"), Weekday::Saturday.to_string());
789/// assert_eq!(String::from("Sunday"), Weekday::Sunday.to_string());
790///
791/// assert_eq!(String::from("Monday"), format!("{}", Weekday::Monday));
792/// assert_eq!(String::from("DayAfterMonday"), format!("{}", Weekday::Tuesday));
793/// assert_eq!(String::from("Wednesday"), format!("{}", Weekday::Wednesday));
794/// assert_eq!(String::from("Giovedì"), format!("{}", Weekday::Thursday));
795/// assert_eq!(String::from("Friday"), format!("{}", Weekday::Friday));
796/// assert_eq!(String::from("Saturday"), format!("{}", Weekday::Saturday));
797/// assert_eq!(String::from("Sunday"), format!("{}", Weekday::Sunday));
798///
799/// let mut weekdays = Weekday::iter_variants();
800/// assert_eq!(Some(Weekday::Tuesday), weekdays.next());
801/// assert_eq!(Some(Weekday::Wednesday), weekdays.next());
802/// assert_eq!(Some(Weekday::Thursday), weekdays.next());
803/// assert_eq!(Some(Weekday::Friday), weekdays.next());
804/// assert_eq!(Some(Weekday::Saturday), weekdays.next());
805/// assert_eq!(Some(Weekday::Sunday), weekdays.next());
806/// assert_eq!(None, weekdays.next());
807///
808/// let mut weekdays_as_str = Weekday::iter_variants_as_str();
809/// assert_eq!(Some("DayAfterMonday"), weekdays_as_str.next());
810/// assert_eq!(Some("Wednesday"), weekdays_as_str.next());
811/// assert_eq!(Some("Giovedì"), weekdays_as_str.next());
812/// assert_eq!(Some("Friday"), weekdays_as_str.next());
813/// assert_eq!(Some("Saturday"), weekdays_as_str.next());
814/// assert_eq!(Some("Sunday"), weekdays_as_str.next());
815/// assert_eq!(None, weekdays.next());
816///
817/// let mut weekdays_as_str_abbr = Weekday::iter_variants_as_str_abbr();
818/// assert_eq!(Some("tue"), weekdays_as_str_abbr.next());
819/// assert_eq!(Some("wed"), weekdays_as_str_abbr.next());
820/// assert_eq!(Some("gio"), weekdays_as_str_abbr.next());
821/// assert_eq!(Some("Fri"), weekdays_as_str_abbr.next());
822/// assert_eq!(Some("Sat"), weekdays_as_str_abbr.next());
823/// assert_eq!(Some("Sun"), weekdays_as_str_abbr.next());
824/// assert_eq!(None, weekdays.next());
825///
826/// assert_eq!(
827/// "\"DayAfterMonday\", \"Wednesday\", \"Giovedì\", \"Friday\", \"Saturday\", \"Sunday\"",
828/// Weekday::variants_list_str(),
829/// );
830///
831/// assert_eq!(
832/// "\"tue\", \"wed\", \"gio\", \"Fri\", \"Sat\", \"Sun\"",
833/// Weekday::variants_list_str_abbr(),
834/// );
835///
836/// // The enum has been marked as `from_str`, so `std::str::FromStr` implementation is available.
837/// assert_eq!(Ok(Weekday::Monday), <Weekday as FromStr>::from_str("Monday"));
838/// assert_eq!(Ok(Weekday::Tuesday), <Weekday as FromStr>::from_str("DayAfterMonday"));
839/// assert_eq!(Ok(Weekday::Wednesday), <Weekday as FromStr>::from_str("Wednesday"));
840/// assert_eq!(Ok(Weekday::Thursday), <Weekday as FromStr>::from_str("Giovedì"));
841/// assert_eq!(Ok(Weekday::Friday), <Weekday as FromStr>::from_str("Friday"));
842/// assert_eq!(Ok(Weekday::Saturday), <Weekday as FromStr>::from_str("Saturday"));
843/// assert_eq!(Ok(Weekday::Sunday), <Weekday as FromStr>::from_str("Sunday"));
844///
845/// assert_eq!(Ok(Weekday::Monday), <Weekday as FromStr>::from_str("Mon"));
846/// assert_eq!(Ok(Weekday::Tuesday), <Weekday as FromStr>::from_str("tue"));
847/// assert_eq!(Ok(Weekday::Wednesday), <Weekday as FromStr>::from_str("wed"));
848/// assert_eq!(Ok(Weekday::Thursday), <Weekday as FromStr>::from_str("gio"));
849/// assert_eq!(Ok(Weekday::Friday), <Weekday as FromStr>::from_str("Fri"));
850/// assert_eq!(Ok(Weekday::Saturday), <Weekday as FromStr>::from_str("Sat"));
851/// assert_eq!(Ok(Weekday::Sunday), <Weekday as FromStr>::from_str("Sun"));
852///
853/// assert_eq!(Err(ParseWeekdayError), <Weekday as FromStr>::from_str("invalid"));
854///
855/// // The enum has been marked as `deserialize`, so `serde::Deserialize` implementation is available.
856/// #[cfg(feature = "serde")]
857/// {
858/// // Deserialize from variant string representation.
859/// assert_eq!(
860/// Ok(Schedule { weekday: Weekday::Monday }),
861/// toml::from_str::<'_, Schedule>("weekday = \"Monday\"\n"),
862/// );
863///
864/// assert_eq!(
865/// Ok(Schedule { weekday: Weekday::Tuesday }),
866/// toml::from_str::<'_, Schedule>("weekday = \"DayAfterMonday\"\n"),
867/// );
868///
869/// assert_eq!(
870/// Ok(Schedule { weekday: Weekday::Wednesday }),
871/// toml::from_str::<'_, Schedule>("weekday = \"Wednesday\"\n"),
872/// );
873///
874/// assert_eq!(
875/// Ok(Schedule { weekday: Weekday::Thursday }),
876/// toml::from_str::<'_, Schedule>("weekday = \"Giovedì\"\n"),
877/// );
878///
879/// assert_eq!(
880/// Ok(Schedule { weekday: Weekday::Friday }),
881/// toml::from_str::<'_, Schedule>("weekday = \"Friday\"\n"),
882/// );
883///
884/// assert_eq!(
885/// Ok(Schedule { weekday: Weekday::Saturday }),
886/// toml::from_str::<'_, Schedule>("weekday = \"Saturday\"\n"),
887/// );
888///
889/// assert_eq!(
890/// Ok(Schedule { weekday: Weekday::Sunday }),
891/// toml::from_str::<'_, Schedule>("weekday = \"Sunday\"\n"),
892/// );
893///
894/// // Deserialize from variant abbreviated string representation.
895/// assert_eq!(
896/// Ok(Schedule { weekday: Weekday::Monday }),
897/// toml::from_str::<'_, Schedule>("weekday = \"Mon\"\n"),
898/// );
899///
900/// assert_eq!(
901/// Ok(Schedule { weekday: Weekday::Tuesday }),
902/// toml::from_str::<'_, Schedule>("weekday = \"tue\"\n"),
903/// );
904///
905/// assert_eq!(
906/// Ok(Schedule { weekday: Weekday::Wednesday }),
907/// toml::from_str::<'_, Schedule>("weekday = \"wed\"\n"),
908/// );
909///
910/// assert_eq!(
911/// Ok(Schedule { weekday: Weekday::Thursday }),
912/// toml::from_str::<'_, Schedule>("weekday = \"gio\"\n"),
913/// );
914///
915/// assert_eq!(
916/// Ok(Schedule { weekday: Weekday::Friday }),
917/// toml::from_str::<'_, Schedule>("weekday = \"Fri\"\n"),
918/// );
919///
920/// assert_eq!(
921/// Ok(Schedule { weekday: Weekday::Saturday }),
922/// toml::from_str::<'_, Schedule>("weekday = \"Sat\"\n"),
923/// );
924///
925/// assert_eq!(
926/// Ok(Schedule { weekday: Weekday::Sunday }),
927/// toml::from_str::<'_, Schedule>("weekday = \"Sun\"\n"),
928/// );
929/// }
930///
931/// // The enum has been marked as `serialize`, so `serde::Serialize` implementation is available.
932/// #[cfg(feature = "serde")]
933/// {
934/// assert_eq!(
935/// Ok(String::from("weekday = \"Monday\"\n")),
936/// toml::to_string(&Schedule { weekday: Weekday::Monday }),
937/// );
938///
939/// assert_eq!(
940/// Ok(String::from("weekday = \"DayAfterMonday\"\n")),
941/// toml::to_string(&Schedule { weekday: Weekday::Tuesday }),
942/// );
943///
944/// assert_eq!(
945/// Ok(String::from("weekday = \"Wednesday\"\n")),
946/// toml::to_string(&Schedule { weekday: Weekday::Wednesday }),
947/// );
948///
949/// assert_eq!(
950/// Ok(String::from("weekday = \"Giovedì\"\n")),
951/// toml::to_string(&Schedule { weekday: Weekday::Thursday }),
952/// );
953///
954/// assert_eq!(
955/// Ok(String::from("weekday = \"Friday\"\n")),
956/// toml::to_string(&Schedule { weekday: Weekday::Friday }),
957/// );
958///
959/// assert_eq!(
960/// Ok(String::from("weekday = \"Saturday\"\n")),
961/// toml::to_string(&Schedule { weekday: Weekday::Saturday }),
962/// );
963///
964/// assert_eq!(
965/// Ok(String::from("weekday = \"Sunday\"\n")),
966/// toml::to_string(&Schedule { weekday: Weekday::Sunday }),
967/// );
968/// }
969/// # }
970///
971/// ```
972///
973/// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html
974/// [`Copy`]: https://doc.rust-lang.org/std/marker/trait.Copy.html
975/// [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html
976/// [`FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html
977/// [`Deserialize`]: https://docs.rs/serde/latest/serde/trait.Deserialize.html
978/// [`Serialize`]: https://docs.rs/serde/latest/serde/trait.Serialize.html
979#[proc_macro_derive(Variants, attributes(variants))]
980pub fn derive_enum_variants(input: TokenStream) -> TokenStream {
981 let input = syn::parse_macro_input!(input as DeriveInput);
982
983 derive_enum_variants_impl(&input)
984 .unwrap_or_else(syn::Error::into_compile_error)
985 .into()
986}
987
988#[cfg(test)]
989mod test {
990 #[test]
991 fn expand() {
992 macrotest::expand("tests/expand/*.rs");
993 }
994
995 #[test]
996 fn expand_serde() {
997 macrotest::expand_args("tests/expand/serde/*.rs", &["--features", "serde"]);
998 }
999
1000 #[test]
1001 fn error() {
1002 let test = trybuild::TestCases::new();
1003 test.compile_fail("tests/fail/*.rs");
1004 }
1005}