Skip to content

Commit bb13390

Browse files
authored
Implement a suffix attribute for serialization of enum variants (#440)
* test: add default test for prefix kw * feat: implement suffix enum attribute similarly to the `prefix` attribute, implement a corresponding attribute that suffixes enum variants. issue: fixes #392 * test: add tests for new suffix attribute * test: add tests checking prefix + suffix attributes together * style: run cargo fmt * doc: update docs for new suffix attribute
1 parent c9e52bf commit bb13390

File tree

13 files changed

+183
-18
lines changed

13 files changed

+183
-18
lines changed

strum_macros/src/helpers/metadata.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub mod kw {
1919
custom_keyword!(const_into_str);
2020
custom_keyword!(use_phf);
2121
custom_keyword!(prefix);
22+
custom_keyword!(suffix);
2223
custom_keyword!(parse_err_ty);
2324
custom_keyword!(parse_err_fn);
2425

@@ -56,6 +57,10 @@ pub enum EnumMeta {
5657
kw: kw::prefix,
5758
prefix: LitStr,
5859
},
60+
Suffix {
61+
kw: kw::suffix,
62+
suffix: LitStr,
63+
},
5964
ParseErrTy {
6065
kw: kw::parse_err_ty,
6166
path: Path,
@@ -94,6 +99,11 @@ impl Parse for EnumMeta {
9499
input.parse::<Token![=]>()?;
95100
let prefix = input.parse()?;
96101
Ok(EnumMeta::Prefix { kw, prefix })
102+
} else if lookahead.peek(kw::suffix) {
103+
let kw = input.parse::<kw::suffix>()?;
104+
input.parse::<Token![=]>()?;
105+
let suffix = input.parse()?;
106+
Ok(EnumMeta::Suffix { kw, suffix })
97107
} else if lookahead.peek(kw::parse_err_ty) {
98108
let kw = input.parse::<kw::parse_err_ty>()?;
99109
input.parse::<Token![=]>()?;

strum_macros/src/helpers/type_props.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub struct StrumTypeProperties {
2424
pub discriminant_vis: Option<Visibility>,
2525
pub use_phf: bool,
2626
pub prefix: Option<LitStr>,
27+
pub suffix: Option<LitStr>,
2728
pub enum_repr: Option<TokenStream>,
2829
pub const_into_str: bool,
2930
pub discriminant_docs: Vec<LitStr>,
@@ -43,6 +44,7 @@ impl HasTypeProperties for DeriveInput {
4344
let mut use_phf_kw = None;
4445
let mut crate_module_path_kw = None;
4546
let mut prefix_kw = None;
47+
let mut suffix_kw = None;
4648
let mut const_into_str = None;
4749

4850
for meta in strum_meta {
@@ -90,6 +92,14 @@ impl HasTypeProperties for DeriveInput {
9092
prefix_kw = Some(kw);
9193
output.prefix = Some(prefix);
9294
}
95+
EnumMeta::Suffix { suffix, kw } => {
96+
if let Some(fst_kw) = suffix_kw {
97+
return Err(occurrence_error(fst_kw, kw, "suffix"));
98+
}
99+
100+
suffix_kw = Some(kw);
101+
output.suffix = Some(suffix);
102+
}
93103
EnumMeta::ParseErrTy { path, kw } => {
94104
if let Some(fst_kw) = parse_err_ty_kw {
95105
return Err(occurrence_error(fst_kw, kw, "parse_err_ty"));

strum_macros/src/helpers/variant_props.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ impl StrumVariantProperties {
3535
&self,
3636
case_style: Option<CaseStyle>,
3737
prefix: Option<&LitStr>,
38+
suffix: Option<&LitStr>,
3839
) -> LitStr {
3940
let mut output = self.to_string.as_ref().cloned().unwrap_or_else(|| {
4041
self.serialize
@@ -48,6 +49,10 @@ impl StrumVariantProperties {
4849
output = LitStr::new(&(prefix.value() + &output.value()), output.span());
4950
}
5051

52+
if let Some(suffix) = suffix {
53+
output = LitStr::new(&(output.value() + &suffix.value()), output.span());
54+
}
55+
5156
output
5257
}
5358

strum_macros/src/lib.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,18 @@ pub fn from_string(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
180180
///
181181
/// assert_eq!("/redred", ColorWithPrefix::Red.as_ref());
182182
/// assert_eq!("/Green", ColorWithPrefix::Green.as_ref());
183+
///
184+
/// // With suffix on all variants
185+
/// #[derive(AsRefStr, Debug)]
186+
/// #[strum(suffix = ".rs")]
187+
/// enum ColorWithSuffix {
188+
/// #[strum(serialize = "redred")]
189+
/// Red,
190+
/// Green,
191+
/// }
192+
///
193+
/// assert_eq!("redred.rs", ColorWithSuffix::Red.as_ref());
194+
/// assert_eq!("Green.rs", ColorWithSuffix::Green.as_ref());
183195
/// ```
184196
#[proc_macro_derive(AsRefStr, attributes(strum))]
185197
pub fn as_ref_str(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
@@ -381,7 +393,9 @@ pub fn to_string(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
381393
/// 3. The name of the variant will be used if there are no `serialize` or `to_string` attributes.
382394
/// 4. If the enum has a `strum(prefix = "some_value_")`, every variant will have that prefix prepended
383395
/// to the serialization.
384-
/// 5. Enums with fields support string interpolation.
396+
/// 5. If the enum has a `strum(suffix = "_another_value")`, every variant will have that suffix appended
397+
/// to the serialization.
398+
/// 6. Enums with fields support string interpolation.
385399
/// Note this means the variant will not "round trip" if you then deserialize the string.
386400
///
387401
/// ```rust

strum_macros/src/macros/enum_variant_names.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,11 @@ pub fn enum_variant_names_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
2222
.iter()
2323
.map(|v| {
2424
let props = v.get_variant_properties()?;
25-
Ok(props
26-
.get_preferred_name(type_properties.case_style, type_properties.prefix.as_ref()))
25+
Ok(props.get_preferred_name(
26+
type_properties.case_style,
27+
type_properties.prefix.as_ref(),
28+
type_properties.suffix.as_ref(),
29+
))
2730
})
2831
.collect::<syn::Result<Vec<LitStr>>>()?;
2932

strum_macros/src/macros/strings/as_ref_str.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,11 @@ where
4040
// Look at all the serialize attributes.
4141
// Use `to_string` attribute (not `as_ref_str` or something) to keep things consistent
4242
// (i.e. always `enum.as_ref().to_string() == enum.to_string()`).
43-
let output = variant_properties
44-
.get_preferred_name(type_properties.case_style, type_properties.prefix.as_ref());
43+
let output = variant_properties.get_preferred_name(
44+
type_properties.case_style,
45+
type_properties.prefix.as_ref(),
46+
type_properties.suffix.as_ref(),
47+
);
4548
let params = match variant.fields {
4649
Fields::Unit => quote! {},
4750
Fields::Unnamed(..) => quote! { (..) },

strum_macros/src/macros/strings/display.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,11 @@ pub fn display_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
3636
}
3737

3838
// Look at all the serialize attributes.
39-
let output = variant_properties
40-
.get_preferred_name(type_properties.case_style, type_properties.prefix.as_ref());
39+
let output = variant_properties.get_preferred_name(
40+
type_properties.case_style,
41+
type_properties.prefix.as_ref(),
42+
type_properties.suffix.as_ref(),
43+
);
4144

4245
let params = match variant.fields {
4346
Fields::Unit => quote! {},

strum_macros/src/macros/strings/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ where
2323
Fields::Unnamed(f) if f.unnamed.len() == 1 => {
2424
let ident = &quote! { field0 };
2525
let ref_kw = match f.unnamed.last().unwrap().ty {
26-
syn::Type::Reference(..) => quote! { },
26+
syn::Type::Reference(..) => quote! {},
2727
_ => quote! { ref },
2828
};
2929

@@ -33,7 +33,7 @@ where
3333
Fields::Named(f) if f.named.len() == 1 => {
3434
let field = f.named.last().unwrap();
3535
let ref_kw = match field.ty {
36-
syn::Type::Reference(..) => quote! { },
36+
syn::Type::Reference(..) => quote! {},
3737
_ => quote! { ref },
3838
};
3939

strum_macros/src/macros/strings/to_string.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,11 @@ pub fn to_string_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
3939
}
4040

4141
// Look at all the serialize attributes.
42-
let output = variant_properties
43-
.get_preferred_name(type_properties.case_style, type_properties.prefix.as_ref());
42+
let output = variant_properties.get_preferred_name(
43+
type_properties.case_style,
44+
type_properties.prefix.as_ref(),
45+
type_properties.suffix.as_ref(),
46+
);
4447

4548
let params = match variant.fields {
4649
Fields::Unit => quote! {},

strum_tests/tests/as_ref_str.rs

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![allow(deprecated)]
22

33
use std::str::FromStr;
4-
use strum::{AsRefStr, AsStaticRef, AsStaticStr, EnumString, IntoStaticStr};
4+
use strum::{AsRefStr, AsStaticRef, AsStaticStr, Display, EnumString, IntoStaticStr};
55

66
mod core {} // ensure macros call `::core`
77

@@ -61,7 +61,13 @@ fn as_green_str() {
6161
#[test]
6262
fn as_fuchsia_str() {
6363
assert_eq!("Purple", (Color::Inner(InnerColor::Purple)).as_ref());
64-
assert_eq!("Purple", (Color::InnerField { inner: InnerColor::Purple }).as_ref());
64+
assert_eq!(
65+
"Purple",
66+
(Color::InnerField {
67+
inner: InnerColor::Purple
68+
})
69+
.as_ref()
70+
);
6571
}
6672

6773
#[derive(IntoStaticStr)]
@@ -128,7 +134,9 @@ enum Brightness {
128134
#[strum(transparent)]
129135
Gray(&'static str),
130136
#[strum(transparent)]
131-
Grey { inner: &'static str }
137+
Grey {
138+
inner: &'static str,
139+
},
132140
}
133141

134142
#[test]
@@ -145,7 +153,10 @@ fn brightness_serialize_all() {
145153
assert_eq!("dim", <&'static str>::from(Brightness::Dim { glow: 0 }));
146154
assert_eq!("Bright", <&'static str>::from(Brightness::BrightWhite));
147155
assert_eq!("Gray", <&'static str>::from(Brightness::Gray("Gray")));
148-
assert_eq!("Grey", <&'static str>::from(Brightness::Grey { inner: "Grey" }));
156+
assert_eq!(
157+
"Grey",
158+
<&'static str>::from(Brightness::Grey { inner: "Grey" })
159+
);
149160
}
150161

151162
#[derive(IntoStaticStr)]
@@ -226,3 +237,38 @@ fn test_const_into_static_str() {
226237
const BRIGHT_WHITE: &'static str = BrightnessConst::BrightWhite.into_str();
227238
assert_eq!("Bright", BRIGHT_WHITE);
228239
}
240+
241+
#[derive(AsRefStr, Display, EnumString)]
242+
#[strum(prefix = "/etc/data/assets/", suffix = ".json")]
243+
enum Asset {
244+
#[strum(serialize = "cfg", serialize = "configuration", to_string = "config")]
245+
Config,
246+
#[strum(serialize = "params")]
247+
Params,
248+
#[strum(default)]
249+
Generic(String),
250+
}
251+
252+
#[test]
253+
fn test_prefix_and_suffix() {
254+
assert_eq!(
255+
String::from("/etc/data/assets/config.json"),
256+
(Asset::Config).to_string()
257+
);
258+
assert_eq!("/etc/data/assets/config.json", (Asset::Config).as_ref(),);
259+
260+
assert_eq!(
261+
String::from("/etc/data/assets/params.json"),
262+
(Asset::Params).to_string()
263+
);
264+
assert_eq!("/etc/data/assets/params.json", (Asset::Params).as_ref());
265+
266+
assert_eq!(
267+
String::from("some-file"),
268+
(Asset::Generic("some-file".into()).to_string()),
269+
);
270+
assert_eq!(
271+
"/etc/data/assets/Generic.json",
272+
(Asset::Generic("()".into()).as_ref()),
273+
)
274+
}

0 commit comments

Comments
 (0)