Skip to content

Commit

Permalink
add default_to_string attribute
Browse files Browse the repository at this point in the history
This attribute causes the to_string to use the value of a
tuple-like variant to be used as the to_string value.
E.g. Color::Green("lime").to_string() will equal "lime" not "Green"

Fixes: how to round trip Display and EnumString with default="true" #86
  • Loading branch information
joshka committed May 16, 2023
1 parent 025b1b5 commit efd45bc
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 1 deletion.
11 changes: 11 additions & 0 deletions strum/src/additional_attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,17 @@
//! The plugin will fail if the data doesn't implement From<&str>. You can only have one `default`
//! on your enum.
//!
//! - `default_to_string`: Similar to `to_string` but applied to a single variant of an enum.
//! The generated code for to_string() will use the value of the data. E.g.
//!
//! ```text
//! enum Color {
//! #[strum(default, default_to_string)]
//! Green(String),
//! }
//! assert_eq!(Color::Green("lime").to_string(), "lime");
//! ```
//!
//! - `disabled`: removes variant from generated code.
//!
//! - `ascii_case_insensitive`: makes the comparison to this variant case insensitive (ASCII only).
Expand Down
5 changes: 5 additions & 0 deletions strum_macros/src/helpers/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub mod kw {
custom_keyword!(to_string);
custom_keyword!(disabled);
custom_keyword!(default);
custom_keyword!(default_to_string);
custom_keyword!(props);
custom_keyword!(ascii_case_insensitive);
}
Expand Down Expand Up @@ -178,6 +179,7 @@ pub enum VariantMeta {
},
Disabled(kw::disabled),
Default(kw::default),
DefaultToString(kw::default_to_string),
AsciiCaseInsensitive {
kw: kw::ascii_case_insensitive,
value: bool,
Expand Down Expand Up @@ -215,6 +217,8 @@ impl Parse for VariantMeta {
Ok(VariantMeta::Disabled(input.parse()?))
} else if lookahead.peek(kw::default) {
Ok(VariantMeta::Default(input.parse()?))
} else if lookahead.peek(kw::default_to_string) {
Ok(VariantMeta::DefaultToString(input.parse()?))
} else if lookahead.peek(kw::ascii_case_insensitive) {
let kw = input.parse()?;
let value = if input.peek(Token![=]) {
Expand Down Expand Up @@ -266,6 +270,7 @@ impl Spanned for VariantMeta {
VariantMeta::ToString { kw, .. } => kw.span,
VariantMeta::Disabled(kw) => kw.span,
VariantMeta::Default(kw) => kw.span,
VariantMeta::DefaultToString(kw) => kw.span,
VariantMeta::AsciiCaseInsensitive { kw, .. } => kw.span,
VariantMeta::Props { kw, .. } => kw.span,
}
Expand Down
9 changes: 9 additions & 0 deletions strum_macros/src/helpers/variant_props.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub trait HasStrumVariantProperties {
pub struct StrumVariantProperties {
pub disabled: Option<kw::disabled>,
pub default: Option<kw::default>,
pub default_to_string: Option<kw::default_to_string>,
pub ascii_case_insensitive: Option<bool>,
pub message: Option<LitStr>,
pub detailed_message: Option<LitStr>,
Expand Down Expand Up @@ -65,6 +66,7 @@ impl HasStrumVariantProperties for Variant {
let mut to_string_kw = None;
let mut disabled_kw = None;
let mut default_kw = None;
let mut default_to_string_kw = None;
let mut ascii_case_insensitive_kw = None;
for meta in self.get_metadata()? {
match meta {
Expand Down Expand Up @@ -114,6 +116,13 @@ impl HasStrumVariantProperties for Variant {
default_kw = Some(kw);
output.default = Some(kw);
}
VariantMeta::DefaultToString(kw) => {
if let Some(fst_kw) = default_to_string_kw {
return Err(occurrence_error(fst_kw, kw, "default"));
}
default_to_string_kw = Some(kw);
output.default_to_string = Some(kw);
}
VariantMeta::AsciiCaseInsensitive { kw, value } => {
if let Some(fst_kw) = ascii_case_insensitive_kw {
return Err(occurrence_error(fst_kw, kw, "ascii_case_insensitive"));
Expand Down
4 changes: 4 additions & 0 deletions strum_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ fn debug_print_generated(ast: &DeriveInput, toks: &TokenStream) {
/// variant. There is an option to match on different case conversions through the
/// `#[strum(serialize_all = "snake_case")]` type attribute.
///
/// The `default_to_string` attribute can be applied to a tuple variant with a single data parameter.
/// The generated to_string() method will use the data parameter to generate the string rather than
/// the name of the variant.
///
/// See the [Additional Attributes](https://docs.rs/strum/0.22/strum/additional_attributes/index.html)
/// Section for more information on using this feature.
///
Expand Down
16 changes: 15 additions & 1 deletion strum_macros/src/macros/strings/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,21 @@ pub fn display_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
Fields::Named(..) => quote! { {..} },
};

arms.push(quote! { #name::#ident #params => f.pad(#output) });
if variant_properties.default_to_string.is_some() {
match &variant.fields {
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
arms.push(quote! { #name::#ident(ref s) => f.pad(s) });
}
_ => {
return Err(syn::Error::new_spanned(
variant,
"Default only works on newtype structs with a single String field",
))
}
}
} else {
arms.push(quote! { #name::#ident #params => f.pad(#output) });
}
}

if arms.len() < variants.len() {
Expand Down
16 changes: 16 additions & 0 deletions strum_macros/src/macros/strings/to_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,22 @@ pub fn to_string_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
continue;
}

// display variants like Green("lime") as "lime"
if variant_properties.default_to_string.is_some() {
match &variant.fields {
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
arms.push(quote! { #name::#ident(ref s) => ::std::string::String::from(s) });
continue;
}
_ => {
return Err(syn::Error::new_spanned(
variant,
"Default only works on newtype structs with a single String field",
))
}
}
}

// Look at all the serialize attributes.
let output = variant_properties.get_preferred_name(type_properties.case_style);

Expand Down
22 changes: 22 additions & 0 deletions strum_tests/tests/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,28 @@ fn to_red_string() {
assert_eq!(String::from("RedRed"), format!("{}", Color::Red));
}

#[test]
fn to_green_string() {
assert_eq!(
String::from("Green"),
format!("{}", Color::Green("lime".into()))
);
}

#[test]
fn to_green_with_default_to_string() {
#[derive(Debug, Eq, PartialEq, EnumString, Display)]
enum Color {
#[strum(default, default_to_string)]
Green(String),
}

assert_eq!(
String::from("lime"),
format!("{}", Color::Green("lime".into()))
);
}

#[derive(Display, Debug, Eq, PartialEq)]
#[strum(serialize_all = "snake_case")]
enum Brightness {
Expand Down
30 changes: 30 additions & 0 deletions strum_tests/tests/to_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,36 @@ fn to_red_string() {
);
}

#[test]
fn to_green_string() {
assert_eq!(
String::from("Green"),
(Color::Green("lime".into())).to_string()
);
assert_eq!(
Color::Green("lime".into()),
Color::from_str("lime").unwrap()
);
}

#[derive(Debug, Eq, PartialEq, EnumString, ToString)]
enum ColorWithDefaultToString {
#[strum(default, default_to_string)]
Green(String),
}

#[test]
fn to_green_with_default_to_string() {
assert_eq!(
String::from("lime"),
(ColorWithDefaultToString::Green("lime".into())).to_string()
);
assert_eq!(
ColorWithDefaultToString::Green("lime".into()),
ColorWithDefaultToString::from_str("lime").unwrap()
);
}

#[derive(Debug, Eq, PartialEq, ToString)]
#[strum(serialize_all = "snake_case")]
enum Brightness {
Expand Down

0 comments on commit efd45bc

Please sign in to comment.