meithecatte / enumflags2

Rust library for typesystem-assisted bitflags.
Apache License 2.0
113 stars 17 forks source link

Way to support alternative serde format + being able to access the number of variants #43

Open aldanor opened 2 years ago

aldanor commented 2 years ago

Here's a thought, something that happens often in practice: you're deserializing something that looks like

{"flags": ["foo", "baz"]}

into your own bitflags format which looks like

enum Flags { Foo, Bar, Baz }

There's a few ways to do this manually, but wouldn't it be nice if this crate provided some way to do it? Not sure what's the best way, since making it a crate feature would immediately change it for all wrapped enums, whereas it's more of a runtime thing. Perhaps the deserializer could try parsing a sequence if it can't parse an integer? There shouldn't be any ambiguity there. Or maybe a separate public module that can be used with #[serde(with)] - this way some fields can be (de)serialized as ints and some - as sequences.

Also, if doing it by hand and in order to avoid allocations, you might need something like T::N_VARIANTS or ArrayVec<T, T::N_VARIANTS> (if you're sure the flags don't repeat...) so you can instantiate something like [T; T::N_VARIANTS] which currently is not something exposed by any of the traits unless I'm missing something.

aldanor commented 2 years ago

Here's something that sort of works with #[serde(with = ...)] as an example (but would be nice to have it work without allocation when deserializing):

    pub mod enumflags2_seq {
        use serde::{de::DeserializeOwned, ser::SerializeSeq};
        use serde::{Deserialize, Deserializer, Serialize, Serializer};

        use enumflags2::{BitFlag, BitFlags};

        pub fn serialize<S, T>(v: &BitFlags<T>, s: S) -> Result<S::Ok, S::Error>
        where
            S: Serializer,
            T: BitFlag + Serialize,
        {
            let mut seq = s.serialize_seq(Some(v.len()))?;
            for flag in v.iter() {
                seq.serialize_element(&flag)?;
            }
            seq.end()
        }

        pub fn deserialize<'de, D, T>(d: D) -> Result<BitFlags<T>, D::Error>
        where
            D: Deserializer<'de>,
            T: BitFlag + DeserializeOwned,
        {
            // TODO: possible to do without allocation?
            let flags = Vec::<T>::deserialize(d)?;
            Ok(BitFlags::from_iter(flags))
        }
    }
meithecatte commented 1 year ago

Vec::deserialize is not magic. You should have no trouble adapting the implementation to insert directly into a BitFlags instead of going through the Vec.

Attempting to parse both an integer and a sequence could make sense, but there's no equivalent for serializing – you have to make a choice.

If you want to submit a PR with a module for use with #[serde(with = "...")], I'll happily merge that.