Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simple enum keys for HashMaps #49

Open
jayvdb opened this issue Sep 15, 2024 · 7 comments
Open

Simple enum keys for HashMaps #49

jayvdb opened this issue Sep 15, 2024 · 7 comments

Comments

@jayvdb
Copy link
Contributor

jayvdb commented Sep 15, 2024

The first of the tests below passes, however it requires an unusual lower casing of the enum variants otherwise serde fails.

The second of these fails with

must succeed: Error(unknown variant `option1_inner2`, expected `Option1` or `Option2`)

Tests

    #[test]
    fn inner_mapping_with_enum_keys() {
        // TODO: the requirement to use a lowercase serde alias is not ideal/unexpected,
        // it matches uppercase environment variable names.
        #[derive(Debug, Deserialize, Eq, Hash, PartialEq)]
        enum MappingKey {
            #[serde(alias = "option1")]
            Option1,
            #[serde(alias = "option2")]
            Option2,
        }
        #[derive(Debug, Deserialize, Eq, PartialEq)]
        struct Mapping {
            val: HashMap<MappingKey, String>,
        }

        let env = vec![("VAL_OPTION1", "FOO"), ("VAL_OPTION2", "BAR")];
        let t: Mapping = from_iter(env).expect("must succeed");
        assert_eq!(
            t,
            Mapping {
                val: HashMap::from_iter(vec![
                    (MappingKey::Option1, "FOO".to_string()),
                    (MappingKey::Option2, "BAR".to_string())
                ])
            }
        );
    }

    #[test]
    fn double_inner_mapping_with_enum_keys() {
        #[derive(Debug, Deserialize, Eq, Hash, PartialEq)]
        enum MappingKey {
            #[serde(alias = "option1")]
            Option1,
            #[serde(alias = "option2")]
            Option2,
        }

        #[derive(Debug, Deserialize, Eq, Hash, PartialEq)]
        enum MappingKey2 {
            #[serde(alias = "inner1")]
            Inner1,
            #[serde(alias = "inner2")]
            Inner2,
        }

        #[derive(Debug, Deserialize, Eq, PartialEq)]
        struct Mapping {
            val: HashMap<MappingKey, HashMap<MappingKey2, String>>,
        }

        let env = vec![("VAL_OPTION1_INNER2", "FOO"), ("VAL_OPTION2_INNER1", "BAR")];
        let t: Mapping = from_iter(env).expect("must succeed");
        assert_eq!(
            t,
            Mapping {
                val: HashMap::from_iter(vec![
                    (
                        MappingKey::Option1,
                        HashMap::from_iter(vec![(MappingKey2::Inner2, "FOO".to_string())])
                    ),
                    (
                        MappingKey::Option2,
                        HashMap::from_iter(vec![(MappingKey2::Inner1, "BAR".to_string())])
                    ),
                ])
            }
        );
    }
@Xuanwo
Copy link
Owner

Xuanwo commented Sep 15, 2024

For the first case, serde will expand the MappingKey into:

impl<'de> _serde::Deserialize<'de> for MappingKey {
    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,
        }
        #[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),
                    _ => _serde::__private::Err(_serde::de::Error::invalid_value(
                        _serde::de::Unexpected::Unsigned(__value),
                        &"variant index 0 <= i < 2",
                    )),
                }
            }
            fn visit_str<__E>(
                self,
                __value: &str,
            ) -> _serde::__private::Result<Self::Value, __E>
            where
                __E: _serde::de::Error,
            {
                match __value {
                    "Option1" => _serde::__private::Ok(__Field::__field0),
                    "Option2" => _serde::__private::Ok(__Field::__field1),
                    _ => _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"Option1" => _serde::__private::Ok(__Field::__field0),
                    b"Option2" => _serde::__private::Ok(__Field::__field1),
                    _ => {
                        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<MappingKey>,
            lifetime: _serde::__private::PhantomData<&'de ()>,
        }
        impl<'de> _serde::de::Visitor<'de> for __Visitor<'de> {
            type Value = MappingKey;
            fn expecting(
                &self,
                __formatter: &mut _serde::__private::Formatter,
            ) -> _serde::__private::fmt::Result {
                _serde::__private::Formatter::write_str(__formatter, "enum MappingKey")
            }
            fn visit_enum<__A>(
                self,
                __data: __A,
            ) -> _serde::__private::Result<Self::Value, __A::Error>
            where
                __A: _serde::de::EnumAccess<'de>,
            {
                match _serde::de::EnumAccess::variant(__data)? {
                    (__Field::__field0, __variant) => {
                        _serde::de::VariantAccess::unit_variant(__variant)?;
                        _serde::__private::Ok(MappingKey::Option1)
                    }
                    (__Field::__field1, __variant) => {
                        _serde::de::VariantAccess::unit_variant(__variant)?;
                        _serde::__private::Ok(MappingKey::Option2)
                    }
                }
            }
        }
        #[doc(hidden)]
        const VARIANTS: &'static [&'static str] = &["Option1", "Option2"];
        _serde::Deserializer::deserialize_enum(
            __deserializer,
            "MappingKey",
            VARIANTS,
            __Visitor {
                marker: _serde::__private::PhantomData::<MappingKey>,
                lifetime: _serde::__private::PhantomData,
            },
        )
    }
}

I feel like serde-env can't help in this way since we don't know what's the key looks like.

We have a map looks like {"option1": value}, but we can't build a deserializer that can transform it into MappingKey::Option1.

@Xuanwo
Copy link
Owner

Xuanwo commented Sep 15, 2024

#51 will address the first issue.

However, the second issue is more complex as it involves serde-env's logic. We will create a flattened mapping with all existing environment keys, which is necessary for loading into structs. Unfortunately, option1_inner2 is not a valid key for MappingKey1, and there's no way to bypass this.

@Xuanwo
Copy link
Owner

Xuanwo commented Sep 15, 2024

Do you have any ideas on case 2?

@jayvdb
Copy link
Contributor Author

jayvdb commented Sep 15, 2024

My guess is that for case 2 we need to try various prefixes, i.e. split by _, and keep trying until serde is ok with the input

@Xuanwo
Copy link
Owner

Xuanwo commented Sep 15, 2024

My guess is that for case 2 we need to try various prefixes, i.e. split by _, and keep trying until serde is ok with the input

Like ignoring error unknown variant and just keep going?

@Xuanwo
Copy link
Owner

Xuanwo commented Sep 15, 2024

Like ignoring error unknown variant and just keep going?

Tried but seed.deserialize() will move seed which means we can't try again.

@jayvdb
Copy link
Contributor Author

jayvdb commented Sep 15, 2024

Maybe support double-underscore separators to allow the user to tell serde-env where to do the splitting.
e.g.
VAL__OPTION1__INNER2
so then if then enums were like

        #[derive(Debug, Deserialize, Eq, Hash, PartialEq)]
        enum MappingKey {
            #[serde(alias = "option_1")]
            Option1,
            #[serde(alias = "option_2")]
            Option2,
        }

serde-env would know how to handle var VAL__OPTION_1__INNER_2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants