strum
strum copied to clipboard
Extend #[strum(serialize)] to include sum type fields
Sum type enums which require custom serialization cannot use their fields.
Here's what I would like to do:
use std::{str::FromStr, string::ToString};
use strum::{Display, EnumString};
#[derive(Display, EnumString, Debug, PartialEq)]
enum E {
#[strum(serialize = "A!")]
A,
#[strum(serialize = "B: ({a}, {b})")]
B {
a: String,
b: i32,
},
}
fn main() {
// This works
let a1 = E::A;
let a2 = a1.to_string();
let a3 = E::from_str(&*a2).unwrap();
assert_eq!(a1, a3);
// This requires manual implementation
let b1 = E::B {
a: "Hello!".into(),
b: 123,
};
let b2 = b1.to_string();
let b3 = E::from_str(&*b2).unwrap();
assert_eq!(b1, b3);
}
From what I can tell, this should work fine so long as the fields implement FromStr. I'd love to hop on this issue, but please let me know if it sounds infeasible.
We should also consider support for format parameters, but that might come for free.
Related issue: https://github.com/Peternator7/strum/issues/190
Hello @ada-x64, if you are interested, I'd be happy to accept a PR for this feature. It seems super useful. There are quite a few corner cases we need to have figured out though:
- What should
AsRefStrandIntoStaticStrdo when you provide a templatized format? My gut feeling is that they should be disallowed and you can't use this feature with that derive simultaneously, but maybe we can find a better solution. - How should this work with various casing features? The inner values probably won't be affected by the case modifiers, but can we respect the casing for the rest of the parse string.
- Can it work for Tuple like sum types as well?
- What should happen if you use some of the values, but not all of them? Right now, they are
Default::default, and that probably makes sense to continue doing? - What happens if the template references the same argument in multiple places, but when we go to parse them with
FromString, they are different values?
@Peternator7 I had a very quick look into display macro I dont exactly understand whats going on with arms without actually logging a couple of things but it seems you are calling formatter.pad on the value.
thiserror crate seems to be doing this parsing it seems the same use case.
Do you think if it is possible to use write! instead of pad? Reading the docs it seems like padding is handled automatically by format_args! which means even if you dont call pad it will still get padded by the user-provided format! or println!. Am I missing something here?
By this logic it just seems we could check for existence of {write! able format string.
So to answer your questions
- this is for
Displayonly which returns an owned value anyway or perhaps for this particular case a new attribute (fserializeperhaps?) is in order soDisplayandIntoStaticStr,AsRefStrcan be used together. - as you mentioned casing is not really applicable here but if strongly desired another
format!could be used and the result could be cased at the cost of creating another intermediary string - since tuple like variants are simply a comma separated list as long as those types implement
std::fmt::Displayindex based access would just work - I dont quite understand why it would make any difference whether to use all of them or not probably i am missing something here and what does formatting do anything with
Defaultsince the values are already there EnumStringalways felt weird anyway I think it should have had adeserializeoption. Since many people are used to the lingo from serde anyway it would be more intuitive and a design improvement i believe.
To summarise \
- serialize could stay as is and for static generation s_serialize could be introduce or the other way around f_serialize for formatted serialize
- for deserialization it should in my opinion really be called
deserializeusingserializekey for deserialization is confusing - accessing sub elements could be done with very minimal parsing of the provided string (it could be as simple as get raw string, push things to a vec in order so they can be passed as arguments to format in correct order.
- For struct like variants indexing could be based on names and a map holding
ident -> valueand again push them to e vec so they can be passed to format in correct order would do the trick and since this is compile time generated code there would be no runtime penalty for doing so
I think a problem might be ambiguous when parsing,
#[strum(serialize = "B: ({a}, {b})")]
B {
a: String,
b: i32,
},
how would B accept string like B { a: String::from("1, 2"), c: 3}, which gives string B: (1, 2, 3)?
one possible solution is report failure when is happens,
replacing all slots to regex B: \((.*?), (.*?)\), match the string and parse inner capture to corresponding fields.
in the way B: (1, 2, 3) would capture a with "1", and b with "2, 3", then "2, 3".parse::<i32>() would failed.