Serialize/Deserialize Enum with Tuple Variant with RegexSet

I am trying to implement Serialize and Deserialize for an enum I have that has tuple variant for a couple of it's values. I have been struggling to do it right. It's clear the problem has outrun my skill by a mile. Any chance I can get pointed in the right direction? Searching has got me where I'm at. What you see below is an amalgamation of various solutions found and attempts to replicate them, but I'm still stuck. I even tried ChatGpt (3.5), but it has a horrible habit of sending you down a very wrong trail.

I need to be able to serialize to send the data to a database, but I also need to be able to send it to/from json. So for the RegexSet I really only need the patterns as a Vec.

Now I may also be completely architecting this completely 'wrong'. It's for a personal project, so I cam open to changes if I am really hurting myself down the line somehow.

The error I am getting is below. I do not get this error on the Any variant.

Err(Error("invalid type: map, expected a string representing a ScopeKind variant", line: 1, column: 28))

I think this is all the appropriate code (let me know if I need to copy anything else in to this sample):

pub enum ScopeKind{
    Any,
    Accept(RegexSet),
    Deny(RegexSet)
}
impl ScopeKind{
    pub fn inner(&self)->&[String]{
        match self{
            ScopeKind::Any => &[],
            ScopeKind::Accept(a) => a.patterns(),
            ScopeKind::Deny(a) => a.patterns(),
        }
    }
}

impl Serialize for ScopeKind {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match *self {
            ScopeKind::Any => {
                let tv = serializer.serialize_tuple_variant("ScopeKind", 0, "Any", 0)?;
                tv.end()
            }
            ScopeKind::Accept(ref a) => {
                let mut tv = serializer.serialize_tuple_variant("E", 1, "Accept", 1)?;
                let field_val = a.patterns();
                tv.serialize_field(field_val)?;
                tv.end()
            },
            ScopeKind::Deny(ref a) => {
                let mut tv = serializer.serialize_tuple_variant("E", 2, "Deny", 1)?;
                let field_val = a.patterns();
                tv.serialize_field(field_val)?;
                tv.end()
            }
        }
    }
}

impl<'de> Deserialize<'de> for ScopeKind {
    fn deserialize<D>(deserializer: D) -> Result<ScopeKind, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct ScopeKindVisitor;
        impl<'de> serde::de::Visitor<'de> for ScopeKindVisitor {
            type Value = ScopeKind;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("a string representing a ScopeKind variant")
            }

            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
            where
                E: serde::de::Error,
            {
                match value {
                    "Any" => Ok(ScopeKind::Any),
                    "Accept" => Ok(ScopeKind::Accept(RegexSet::new::<&[_; 0], &String>(&[]).unwrap())), // You may need to adjust this depending on how you create RegexSet instances
                    "Deny" => Ok(ScopeKind::Deny(RegexSet::new::<&[_; 0], &String>(&[]).unwrap())), // Adjust this as well
                    _ => Err(serde::de::Error::unknown_variant(value, &["Any", "Accept", "Deny"])),
                }
            }
            
        }
        deserializer.deserialize_str(ScopeKindVisitor)
    }
}

You want to use deserialize_enum along with Visitor::visit_enum. You obviously aren't just deserializing a string here, so I'm not sure why you're calling deserialize_str

Looking at the output of serdes derive macros might be helpful. Here's the cargo --expand output for a similar enum

Expanded derive
enum Test {
    A(usize),
    B(String),
    C,
}

#[doc(hidden)]
#[allow(non_upper_case_globals, unused_attributes, unused_qualifications)]
const _: () = {
    #[allow(unused_extern_crates, clippy::useless_attribute)]
    extern crate serde as _serde;
    #[automatically_derived]
    impl _serde::Serialize for Test {
        fn serialize<__S>(
            &self,
            __serializer: __S,
        ) -> _serde::__private::Result<__S::Ok, __S::Error>
        where
            __S: _serde::Serializer,
        {
            match *self {
                Test::A(ref __field0) => _serde::Serializer::serialize_newtype_variant(
                    __serializer,
                    "Test",
                    0u32,
                    "A",
                    __field0,
                ),
                Test::B(ref __field0) => _serde::Serializer::serialize_newtype_variant(
                    __serializer,
                    "Test",
                    1u32,
                    "B",
                    __field0,
                ),
                Test::C => {
                    _serde::Serializer::serialize_unit_variant(__serializer, "Test", 2u32, "C")
                }
            }
        }
    }
};
#[doc(hidden)]
#[allow(non_upper_case_globals, unused_attributes, unused_qualifications)]
const _: () = {
    #[allow(unused_extern_crates, clippy::useless_attribute)]
    extern crate serde as _serde;
    #[automatically_derived]
    impl<'de> _serde::Deserialize<'de> for Test {
        fn deserialize<__D>(__deserializer: __D) -> _serde::__private::Result<Self, __D::Error>
        where
            __D: _serde::Deserializer<'de>,
        {
            #[allow(non_camel_case_types)]
            #[doc(hidden)]
            enum __Field {
                __field0,
                __field1,
                __field2,
            }
            #[doc(hidden)]
            struct __FieldVisitor;
            impl<'de> _serde::de::Visitor<'de> for __FieldVisitor {
                type Value = __Field;
                fn expecting(
                    &self,
                    __formatter: &mut _serde::__private::Formatter,
                ) -> _serde::__private::fmt::Result {
                    _serde::__private::Formatter::write_str(__formatter, "variant identifier")
                }
                fn visit_u64<__E>(self, __value: u64) -> _serde::__private::Result<Self::Value, __E>
                where
                    __E: _serde::de::Error,
                {
                    match __value {
                        0u64 => _serde::__private::Ok(__Field::__field0),
                        1u64 => _serde::__private::Ok(__Field::__field1),
                        2u64 => _serde::__private::Ok(__Field::__field2),
                        _ => _serde::__private::Err(_serde::de::Error::invalid_value(
                            _serde::de::Unexpected::Unsigned(__value),
                            &"variant index 0 <= i < 3",
                        )),
                    }
                }
                fn visit_str<__E>(
                    self,
                    __value: &str,
                ) -> _serde::__private::Result<Self::Value, __E>
                where
                    __E: _serde::de::Error,
                {
                    match __value {
                        "A" => _serde::__private::Ok(__Field::__field0),
                        "B" => _serde::__private::Ok(__Field::__field1),
                        "C" => _serde::__private::Ok(__Field::__field2),
                        _ => _serde::__private::Err(_serde::de::Error::unknown_variant(
                            __value, VARIANTS,
                        )),
                    }
                }
                fn visit_bytes<__E>(
                    self,
                    __value: &[u8],
                ) -> _serde::__private::Result<Self::Value, __E>
                where
                    __E: _serde::de::Error,
                {
                    match __value {
                        b"A" => _serde::__private::Ok(__Field::__field0),
                        b"B" => _serde::__private::Ok(__Field::__field1),
                        b"C" => _serde::__private::Ok(__Field::__field2),
                        _ => {
                            let __value = &_serde::__private::from_utf8_lossy(__value);
                            _serde::__private::Err(_serde::de::Error::unknown_variant(
                                __value, VARIANTS,
                            ))
                        }
                    }
                }
            }
            impl<'de> _serde::Deserialize<'de> for __Field {
                #[inline]
                fn deserialize<__D>(
                    __deserializer: __D,
                ) -> _serde::__private::Result<Self, __D::Error>
                where
                    __D: _serde::Deserializer<'de>,
                {
                    _serde::Deserializer::deserialize_identifier(__deserializer, __FieldVisitor)
                }
            }
            #[doc(hidden)]
            struct __Visitor<'de> {
                marker: _serde::__private::PhantomData<Test>,
                lifetime: _serde::__private::PhantomData<&'de ()>,
            }
            impl<'de> _serde::de::Visitor<'de> for __Visitor<'de> {
                type Value = Test;
                fn expecting(
                    &self,
                    __formatter: &mut _serde::__private::Formatter,
                ) -> _serde::__private::fmt::Result {
                    _serde::__private::Formatter::write_str(__formatter, "enum Test")
                }
                fn visit_enum<__A>(
                    self,
                    __data: __A,
                ) -> _serde::__private::Result<Self::Value, __A::Error>
                where
                    __A: _serde::de::EnumAccess<'de>,
                {
                    match match _serde::de::EnumAccess::variant(__data) {
                        _serde::__private::Ok(__val) => __val,
                        _serde::__private::Err(__err) => {
                            return _serde::__private::Err(__err);
                        }
                    } {
                        (__Field::__field0, __variant) => _serde::__private::Result::map(
                            _serde::de::VariantAccess::newtype_variant::<usize>(__variant),
                            Test::A,
                        ),
                        (__Field::__field1, __variant) => _serde::__private::Result::map(
                            _serde::de::VariantAccess::newtype_variant::<String>(__variant),
                            Test::B,
                        ),
                        (__Field::__field2, __variant) => {
                            match _serde::de::VariantAccess::unit_variant(__variant) {
                                _serde::__private::Ok(__val) => __val,
                                _serde::__private::Err(__err) => {
                                    return _serde::__private::Err(__err);
                                }
                            };
                            _serde::__private::Ok(Test::C)
                        }
                    }
                }
            }
            #[doc(hidden)]
            const VARIANTS: &'static [&'static str] = &["A", "B", "C"];
            _serde::Deserializer::deserialize_enum(
                __deserializer,
                "Test",
                VARIANTS,
                __Visitor {
                    marker: _serde::__private::PhantomData::<Test>,
                    lifetime: _serde::__private::PhantomData,
                },
            )
        }
    }
};

Personally though, I think using the serde(with =...) derive attribute is less confusing. [1] That lets the derive macro do most of the heavy lifting.

Playground

use regex::RegexSet;
use serde::{Deserialize, Serialize};

mod regex_serde {
    use std::marker::PhantomData;

    use super::RegexSet;
    use serde::{de::Visitor, ser::SerializeSeq, Deserializer, Serializer};

    pub fn serialize<S: Serializer>(set: &RegexSet, serialize: S) -> Result<S::Ok, S::Error> {
        let p = set.patterns();
        let mut seq = serialize.serialize_seq(Some(p.len()))?;

        for e in p {
            seq.serialize_element(&e)?
        }

        seq.end()
    }

    pub fn deserialize<'de, D>(de: D) -> Result<RegexSet, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct SeqVisitor<'de>(PhantomData<&'de ()>);
        impl<'de> Visitor<'de> for SeqVisitor<'de> {
            type Value = RegexSet;

            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
                formatter.write_str("RegexSet")
            }

            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
            where
                A: serde::de::SeqAccess<'de>,
            {
                let mut patterns = Vec::<String>::new();

                if let Some(size) = seq.size_hint() {
                    patterns.reserve(size);
                }

                while let Some(next) = seq.next_element()? {
                    patterns.push(next)
                }

                Ok(RegexSet::new(patterns).unwrap())
            }
        }
        de.deserialize_seq(SeqVisitor(PhantomData))
    }
}

#[derive(Serialize, Deserialize, Debug)]
pub enum ScopeKind {
    Any,
    Accept(#[serde(with = "regex_serde")] RegexSet),
    Deny(#[serde(with = "regex_serde")] RegexSet),
}

impl ScopeKind {
    pub fn inner(&self) -> &[String] {
        match self {
            ScopeKind::Any => &[],
            ScopeKind::Accept(a) => a.patterns(),
            ScopeKind::Deny(a) => a.patterns(),
        }
    }
}

fn main() {
    let json = serde_json::to_string_pretty(&ScopeKind::Accept(RegexSet::new(["a", "b"]).unwrap()))
        .unwrap();

    println!("{json}");

    let de: ScopeKind = serde_json::from_str(&json).unwrap();
    println!("{de:?}")
}

  1. You could also use a wrapper type around RegexSet, but this does basically the same thing ↩︎

It seems to be working. Yay!!

Just so I understand what's changing here: I am removing the need to implement Serialize/Deserialize myself for the type by providing a module just for the serializing/deserializing specifically the RegexSet (which doesn't implement serialize/deserialize already) according to my needs and telling Serde to use that as it's serializing/deserializing method using the "with=" macro. That way the rest can still be handled by Serde?

I was not aware of the 'with=...' functionality. That will make a lot of things a whole lot easier specifically for serde/storage related things. Essentially, I am able to implement Serialize/Deserialize (a foreign trait) for a foreign crate which Rust expressly prohibits but creates SOOOO many nightmares because of it's absence. ---VERY NICE!

High level that's the basics of what is changed here?

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.