Skip to content
Snippets Groups Projects
Select Git revision
  • 4683a4658c6a7b6a88bcf0f285db72b8c616d047
  • master default protected
  • kirovets_doc
  • ypodlesov/unite-projection
  • max/parse-anonymous-blocks
  • sd/constant
  • docs/server_setup
  • ekhamitov/union-types
  • docs/privileges
  • sd/2244-issue
  • raiondesu/1984-used-tier-memory
  • lomakin/adr-client-bucket-awareness
  • lomakin/insert-query-sharding-metadata
  • 25.4 protected
  • ekhamitov/1977-remove-additional-child
  • gmoshkin/box-error-improvements
  • astrochuk/explain-fix
  • dkoltsov/support-https
  • docs/config_storage
  • ekhamitov/2365-fix-flaky
  • kostja-check-quorum
  • 25.4.4 protected
  • 25.3.8 protected
  • 25.3.7 protected
  • 25.4.3 protected
  • 25.3.6 protected
  • 25.4.2 protected
  • 25.3.5 protected
  • 25.4.1 protected
  • 25.5.0 protected
  • 25.3.4 protected
  • 25.3.3 protected
  • 25.3.2 protected
  • 25.4.0 protected
  • 25.3.1 protected
  • 25.2.4 protected
  • 25.2.3 protected
  • 25.2.2 protected
  • 25.2.1 protected
  • 25.3.0 protected
  • 25.1.2 protected
41 results

to_rmpv_named.rs

Blame
  • introspection.rs 27.58 KiB
    //! This module contains items intended to help with runtime introspection of
    //! rust types. Currently it's mainly used for the [`PicodataConfig`] struct
    //! to simplify the management of configuration parameters and relatetd stuff.
    //!
    //! The main functionality is implemented via the [`Introspection`] trait and
    //! the corresponding derive macro. This trait currently facilitates the ability
    //! to set and get fields of the struct via a path known at runtime. Also it
    //! supports some basic struct field information.
    //!
    //! [`PicodataConfig`]: crate::config::PicodataConfig
    use crate::traft::error::Error;
    pub use pico_proc_macro::Introspection;
    
    pub trait Introspection {
        /// Information about the struct fields of `Self`.
        const FIELD_INFOS: &'static [FieldInfo];
    
        /// Assign field of `self` described by `path` to value parsed from a given
        /// `yaml` expression.
        ///
        /// When using the `#[derive(Introspection)]` derive macro the implementation
        /// uses the `serde_yaml` to decode values from yaml. This may change in the
        /// future.
        ///
        /// # Examples:
        /// ```
        /// use picodata::introspection::Introspection;
        ///
        /// #[derive(Introspection, Default)]
        /// #[introspection(crate = picodata)]
        /// struct MyStruct {
        ///     number: i32,
        ///     text: String,
        ///     #[introspection(nested)]
        ///     nested: NestedStruct,
        /// }
        ///
        /// #[derive(Introspection, Default)]
        /// #[introspection(crate = picodata)]
        /// struct NestedStruct {
        ///     sub_field: f32,
        /// }
        ///
        /// let mut s = MyStruct::default();
        /// s.set_field_from_yaml("number", "420").unwrap();
        /// s.set_field_from_yaml("text", "hello world").unwrap();
        /// s.set_field_from_yaml("nested.sub_field", "3.14").unwrap();
        /// ```
        // TODO: maybe remove in favour of set_field_from_rmpv
        fn set_field_from_yaml(&mut self, path: &str, yaml: &str) -> Result<(), IntrospectionError>;
    
        /// Assign field of `self` described by `path` to value converted from the
        /// provided `value`.
        ///
        /// When using the `#[derive(Introspection)]` derive macro the implementation
        /// uses the `rmpv::ext::deserialize_from` to convert to the required type.
        /// This may change in the future.
        ///
        /// # Examples:
        /// ```
        /// use picodata::introspection::Introspection;
        /// use rmpv::Value;
        ///
        /// #[derive(Introspection, Default)]
        /// #[introspection(crate = picodata)]
        /// struct MyStruct {
        ///     number: i32,
        ///     text: String,
        ///     #[introspection(nested)]
        ///     nested: NestedStruct,
        /// }
        ///
        /// #[derive(Introspection, Default)]
        /// #[introspection(crate = picodata)]
        /// struct NestedStruct {
        ///     sub_field: f32,
        /// }
        ///
        /// let mut s = MyStruct::default();
        /// s.set_field_from_rmpv("number", &Value::from(420)).unwrap();
        /// s.set_field_from_rmpv("text", &Value::from("hello world")).unwrap();
        /// s.set_field_from_rmpv("nested.sub_field", &Value::F32(3.14)).unwrap();
        /// ```
        fn set_field_from_rmpv(
            &mut self,
            path: &str,
            value: &rmpv::Value,
        ) -> Result<(), IntrospectionError>;
    
        /// Get field of `self` described by `path` as a generic msgpack value in
        /// form of [`rmpv::Value`].
        ///
        /// When using the `#[derive(Introspection)]` derive macro the implementation
        /// converts the value to msgpack using [`to_rmpv_value`].
        ///
        /// In the future we may want to get values as some other enums (maybe
        /// serde_yaml::Value, or our custom one), but for now we've chosen rmpv
        /// because we're using this to convert values to msgpack.
        ///
        /// # Examples:
        /// ```
        /// use picodata::introspection::Introspection;
        /// use rmpv::Value;
        ///
        /// #[derive(Introspection, Default)]
        /// #[introspection(crate = picodata)]
        /// struct MyStruct {
        ///     number: i32,
        ///     text: String,
        ///     #[introspection(nested)]
        ///     nested: NestedStruct,
        /// }
        ///
        /// #[derive(Introspection, Default)]
        /// #[introspection(crate = picodata)]
        /// struct NestedStruct {
        ///     sub_field: f64,
        /// }
        ///
        /// let mut s = MyStruct {
        ///     number: 13,
        ///     text: "hello".into(),
        ///     nested: NestedStruct {
        ///         sub_field: 2.71,
        ///     },
        /// };
        ///
        /// assert_eq!(s.get_field_as_rmpv("number").unwrap(), Value::from(13));
        /// assert_eq!(s.get_field_as_rmpv("text").unwrap(), Value::from("hello"));
        /// assert_eq!(s.get_field_as_rmpv("nested.sub_field").unwrap(), Value::from(2.71));
        /// ```
        fn get_field_as_rmpv(&self, path: &str) -> Result<rmpv::Value, IntrospectionError>;
    
        /// Get a value which was specified via the `#[introspection(config_default = expr)]`
        /// attribute converted to a [`rmpv::Value`].
        ///
        /// Returns `Ok(None)` if `config_default` attribute wasn't provided for
        /// given field.
        ///
        /// Note that the value is not type checked, instead it is immediately
        /// converted to a rmpv::Value, so it's the user's responsibility to make
        /// sure the resulting value has the appropriate type. This however makes it
        /// a bit simpler to specify values for nested types like `Option<String>`,
        /// etc.
        ///
        /// The `expr` in `#[introspection(config_default = expr)]` may contain
        /// references to `self`, which allows for default values of some parameters
        /// to be dependent on values of other parameters
        /// (see [`InstanceConfig::advertise_address`] for example).
        ///
        /// Also note that this function doesn't do any special checks to see if the
        /// values are set or not in the actual struct. See what happens in
        /// [`PicodataConfig::set_defaults_explicitly`] for a real-life example.
        ///
        /// When using the `#[derive(Introspection)]` derive macro the implementation
        /// converts the value to msgpack using `rmp_serde` and then decodes that
        /// msgpack into a `rmpv::Value`. This is sub-optimal with respect to performance,
        /// but for our use cases this is fine.
        ///
        /// # Examples:
        /// ```
        /// use picodata::introspection::Introspection;
        /// use rmpv::Value;
        ///
        /// #[derive(Introspection, Default)]
        /// #[introspection(crate = picodata)]
        /// struct MyStruct {
        ///     #[introspection(config_default = 69105)]
        ///     number: i32,
        ///     #[introspection(config_default = format!("there's {} leaves in the pile", self.number))]
        ///     text: String,
        ///     doesnt_have_default: bool,
        /// }
        ///
        /// let mut s = MyStruct {
        ///     number: 2,
        ///     ..Default::default()
        /// };
        ///
        /// assert_eq!(s.get_field_default_value_as_rmpv("number").unwrap(), Some(Value::from(69105)));
        ///
        /// // Note here the actual value of `s.number` is used, not the config_default one. --------V
        /// assert_eq!(s.get_field_default_value_as_rmpv("text").unwrap(), Some(Value::from("there's 2 leaves in the pile")));
        ///
        /// assert_eq!(s.get_field_default_value_as_rmpv("doesnt_have_default").unwrap(), None);
        /// ```
        ///
        /// [`InstanceConfig::advertise_address`]: crate::config::InstanceConfig::advertise_address
        /// [`PicodataConfig::set_defaults_explicitly`]: crate::config::PicodataConfig::set_defaults_explicitly
        fn get_field_default_value_as_rmpv(
            &self,
            path: &str,
        ) -> Result<Option<rmpv::Value>, IntrospectionError>;
    }
    
    /// Information about a single struct field. This is the type which is stored
    /// in [`Introspection::FIELD_INFOS`].
    ///
    /// Currently this info is used when reporting errors about wrong field names
    /// and for introspecting structs using for example [`leaf_field_paths`].
    #[derive(PartialEq, Eq, Hash)]
    pub struct FieldInfo {
        pub name: &'static str,
        pub nested_fields: &'static [FieldInfo],
    }
    
    impl std::fmt::Debug for FieldInfo {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            write!(f, "{:?}", self.name)?;
            if !self.nested_fields.is_empty() {
                write!(f, ": {:?}", self.nested_fields)?;
            }
            Ok(())
        }
    }
    
    /// Returns an array of period-separated (e.g. `field.sub_field`) paths
    /// to struct fields including subfields of structs.
    ///
    /// This is basically just a helper method for recursively walking over
    /// the values from [`Introspection::FIELD_INFOS`] array.
    ///
    /// # Examples:
    /// ```
    /// use picodata::introspection::{leaf_field_paths, Introspection};
    ///
    /// #[derive(Introspection, Default)]
    /// #[introspection(crate = picodata)]
    /// struct MyStruct {
    ///     number: i32,
    ///     text: String,
    ///     #[introspection(nested)]
    ///     nested: NestedStruct,
    /// }
    ///
    /// #[derive(Introspection, Default)]
    /// #[introspection(crate = picodata)]
    /// struct NestedStruct {
    ///     sub_field: f32,
    /// }
    ///
    /// assert_eq!(
    ///     leaf_field_paths::<MyStruct>(),
    ///     &["number".to_owned(), "text".to_owned(), "nested.sub_field".to_owned()]
    /// );
    /// ```
    pub fn leaf_field_paths<T>() -> Vec<String>
    where
        T: Introspection,
    {
        // TODO: do the awful ugly rust thing to cache this value
        let mut res = Vec::new();
        recursive_helper("", T::FIELD_INFOS, &mut res);
        return res;
    
        fn recursive_helper(prefix: &str, nested_fields: &[FieldInfo], res: &mut Vec<String>) {
            for field in nested_fields {
                let name = field.name;
                if field.nested_fields.is_empty() {
                    res.push(format!("{prefix}{name}"));
                } else {
                    recursive_helper(&format!("{prefix}{name}."), field.nested_fields, res);
                }
            }
        }
    }
    
    /// A public reimport for use in the derive macro.
    pub use rmpv::Value as RmpvValue;
    
    /// Converts a [`rmpv::Value`] `value` to a generic serde deserializable type.
    /// This function is just needed to be called from the derived
    /// [`Introspection::set_field_from_rmpv`] implementations.
    #[inline(always)]
    pub fn from_rmpv_value<T>(value: &rmpv::Value) -> Result<T, Error>
    where
        T: for<'de> serde::Deserialize<'de>,
    {
        rmpv::ext::deserialize_from(value.as_ref()).map_err(Error::other)
    }
    
    /// Converts a generic serde serializable value to [`rmpv::Value`]. This
    /// function is just needed to be called from the derived
    /// [`Introspection::get_field_as_rmpv`] implementations.
    #[inline(always)]
    pub fn to_rmpv_value<T>(v: &T) -> Result<rmpv::Value, Error>
    where
        T: serde::Serialize,
    {
        crate::to_rmpv_named::to_rmpv_named(v).map_err(Error::other)
    }
    
    #[derive(Debug, thiserror::Error)]
    pub enum IntrospectionError {
        #[error("{}", Self::no_such_field_error_message(.parent, .field, .expected))]
        NoSuchField {
            parent: String,
            field: String,
            expected: &'static [FieldInfo],
        },
    
        #[error("incorrect value for field '{field}': {error}")]
        ConvertToFieldError {
            field: String,
            error: Box<dyn std::error::Error>,
        },
    
        #[error("{expected} '{path}'")]
        InvalidPath {
            path: String,
            expected: &'static str,
        },
    
        #[error("field '{field}' has no nested sub-fields")]
        NotNestable { field: String },
    
        #[error("field '{field}' cannot be assigned directly, must choose a sub-field (for example '{field}.{example}')")]
        AssignToNested {
            field: String,
            example: &'static str,
        },
    
        #[error("failed converting '{field}' to a msgpack value: {details}")]
        ToRmpvValue { field: String, details: Error },
    }
    
    impl IntrospectionError {
        fn no_such_field_error_message(parent: &str, field: &str, expected: &[FieldInfo]) -> String {
            let mut res = String::with_capacity(128);
            if !parent.is_empty() {
                _ = write!(&mut res, "{parent}: ");
            }
            use std::fmt::Write;
            _ = write!(&mut res, "unknown field `{field}`");
    
            let mut fields = expected.iter();
            if let Some(first) = fields.next() {
                _ = write!(&mut res, ", expected one of `{}`", first.name);
                for next in fields {
                    _ = write!(&mut res, ", `{}`", next.name);
                }
            } else {
                _ = write!(&mut res, ", there are no fields at all");
            }
    
            res
        }
    
        pub fn with_prepended_prefix(mut self, prefix: &str) -> Self {
            match &mut self {
                Self::NotNestable { field } => {
                    *field = format!("{prefix}.{field}");
                }
                Self::InvalidPath { path, .. } => {
                    *path = format!("{prefix}.{path}");
                }
                Self::NoSuchField { parent, .. } => {
                    if parent.is_empty() {
                        *parent = prefix.into();
                    } else {
                        *parent = format!("{prefix}.{parent}");
                    }
                }
                Self::ConvertToFieldError { field, .. } => {
                    *field = format!("{prefix}.{field}");
                }
                Self::AssignToNested { field, .. } => {
                    *field = format!("{prefix}.{field}");
                }
                Self::ToRmpvValue { field, .. } => {
                    *field = format!("{prefix}.{field}");
                }
            }
            self
        }
    }
    
    #[cfg(test)]
    mod test {
        use super::*;
        use pretty_assertions::assert_eq;
    
        #[derive(Default, Debug, Introspection, PartialEq)]
        struct S {
            x: i32,
            #[introspection(config_default = self.x as f32 * 0.5)]
            y: f32,
            #[introspection(config_default = "this is a &str but it still works")]
            s: String,
            #[introspection(config_default = &["this", "also", "works"])]
            v: Vec<String>,
            #[introspection(nested)]
            r#struct: Nested,
    
            #[introspection(ignore)]
            ignored: serde_yaml::Value,
        }
    
        #[derive(Default, Debug, Introspection, PartialEq)]
        struct Nested {
            #[introspection(config_default = "nested of course works")]
            a: String,
            #[introspection(config_default = format!("{}, but type safety is missing unfortunately", self.a))]
            b: i64,
            #[introspection(nested)]
            empty: Empty,
        }
    
        #[derive(Default, Debug, Introspection, PartialEq)]
        struct Empty {}
    
        #[test]
        fn set_field_from_yaml() {
            let mut s = S::default();
    
            //
            // Check `set_field_from_yaml` error cases
            //
            let e = s.set_field_from_yaml("a", "foo").unwrap_err();
            assert_eq!(
                e.to_string(),
                "unknown field `a`, expected one of `x`, `y`, `s`, `v`, `struct`"
            );
    
            let e = s.set_field_from_yaml(".x", "foo").unwrap_err();
            assert_eq!(e.to_string(), "expected a field name before '.x'");
    
            let e = s.set_field_from_yaml("&-*%?!", "foo").unwrap_err();
            assert_eq!(
                e.to_string(),
                "unknown field `&-*%?!`, expected one of `x`, `y`, `s`, `v`, `struct`"
            );
    
            let e = s.set_field_from_yaml("x.foo.bar", "foo").unwrap_err();
            assert_eq!(e.to_string(), "field 'x' has no nested sub-fields");
    
            let e = s.set_field_from_yaml("struct", "foo").unwrap_err();
            assert_eq!(e.to_string(), "field 'struct' cannot be assigned directly, must choose a sub-field (for example 'struct.a')");
    
            let e = s.set_field_from_yaml("struct.empty", "foo").unwrap_err();
            assert_eq!(e.to_string(), "field 'struct.empty' cannot be assigned directly, must choose a sub-field (for example 'struct.empty.<actually there's no fields in this struct :(>')");
    
            let e = s.set_field_from_yaml("struct.foo", "foo").unwrap_err();
            assert_eq!(
                e.to_string(),
                "struct: unknown field `foo`, expected one of `a`, `b`, `empty`"
            );
    
            let e = s.set_field_from_yaml("struct.a.bar", "foo").unwrap_err();
            assert_eq!(e.to_string(), "field 'struct.a' has no nested sub-fields");
    
            let e = s.set_field_from_yaml("struct.a..", "foo").unwrap_err();
            assert_eq!(e.to_string(), "expected a field name after 'struct.a.'");
    
            let e = s.set_field_from_yaml("x.", "foo").unwrap_err();
            assert_eq!(e.to_string(), "expected a field name after 'x.'");
    
            let e = s.set_field_from_yaml("x", "foo").unwrap_err();
            assert_eq!(
                e.to_string(),
                "incorrect value for field 'x': invalid type: string \"foo\", expected i32"
            );
    
            let e = s.set_field_from_yaml("x", "'420'").unwrap_err();
            assert_eq!(
                e.to_string(),
                "incorrect value for field 'x': invalid type: string \"420\", expected i32"
            );
    
            let e = s.set_field_from_yaml("x", "'420'").unwrap_err();
            assert_eq!(
                e.to_string(),
                "incorrect value for field 'x': invalid type: string \"420\", expected i32"
            );
    
            let e = s.set_field_from_yaml("ignored", "foo").unwrap_err();
            assert_eq!(
                e.to_string(),
                "unknown field `ignored`, expected one of `x`, `y`, `s`, `v`, `struct`"
            );
            assert_eq!(s.ignored, serde_yaml::Value::default());
    
            let e = s
                .set_field_from_yaml("struct.empty.foo", "bar")
                .unwrap_err();
            assert_eq!(
                e.to_string(),
                "struct.empty: unknown field `foo`, there are no fields at all"
            );
    
            //
            // Check `set_field_from_yaml` success cases
            //
            s.set_field_from_yaml("v", "[1, 2, 3]").unwrap();
            assert_eq!(&s.v, &["1", "2", "3"]);
            s.set_field_from_yaml("v", "['foo', \"bar\", baz]").unwrap();
            assert_eq!(&s.v, &["foo", "bar", "baz"]);
    
            s.set_field_from_yaml("x", "420").unwrap();
            assert_eq!(s.x, 420);
    
            s.set_field_from_yaml("y", "13").unwrap();
            assert_eq!(s.y, 13.0);
            s.set_field_from_yaml("y", "13.37").unwrap();
            assert_eq!(s.y, 13.37);
    
            s.set_field_from_yaml("s", "13.37").unwrap();
            assert_eq!(s.s, "13.37");
            s.set_field_from_yaml("s", "foo bar").unwrap();
            assert_eq!(s.s, "foo bar");
            s.set_field_from_yaml("s", "'foo bar'").unwrap();
            assert_eq!(s.s, "foo bar");
    
            s.set_field_from_yaml("  struct  .  a  ", "aaaa").unwrap();
            assert_eq!(s.r#struct.a, "aaaa");
            s.set_field_from_yaml("struct.b", "  0xbbbb  ").unwrap();
            assert_eq!(s.r#struct.b, 0xbbbb);
        }
    
        #[test]
        fn set_field_from_rmpv() {
            let mut s = S::default();
    
            //
            // Error cases
            //
            let e = s
                .set_field_from_rmpv("x", &rmpv::Value::Boolean(true))
                .unwrap_err();
            assert_eq!(e.to_string(), "incorrect value for field 'x': error while decoding value: invalid type: boolean `true`, expected i32");
    
            let e = s
                .set_field_from_rmpv("struct.a", &rmpv::Value::Nil)
                .unwrap_err();
            assert_eq!(e.to_string(), "incorrect value for field 'struct.a': error while decoding value: invalid type: unit value, expected a string");
    
            let e = s
                .set_field_from_rmpv("struct", &rmpv::Value::Nil)
                .unwrap_err();
            assert_eq!(e.to_string(), "field 'struct' cannot be assigned directly, must choose a sub-field (for example 'struct.a')");
    
            // Other error cases are mostly the same as in case of `set_field_from_yaml`
    
            //
            // Success cases
            //
            s.set_field_from_rmpv("x", &rmpv::Value::from(123)).unwrap();
            s.set_field_from_rmpv("y", &rmpv::Value::F32(3.21)).unwrap();
            s.set_field_from_rmpv("s", &rmpv::Value::from("ccc"))
                .unwrap();
            s.set_field_from_rmpv(
                "v",
                &rmpv::Value::Array(vec![rmpv::Value::from("Vv"), rmpv::Value::from("vV")]),
            )
            .unwrap();
    
            s.set_field_from_rmpv("struct.a", &("AaAa".into())).unwrap();
            s.set_field_from_rmpv("struct.b", &(0xccc.into())).unwrap();
            assert_eq!(
                s,
                S {
                    x: 123,
                    y: 3.21,
                    s: "ccc".into(),
                    v: vec!["Vv".into(), "vV".into()],
                    r#struct: Nested {
                        a: "AaAa".into(),
                        b: 0xccc,
                        empty: Empty {},
                    },
                    ignored: Default::default(),
                },
            );
        }
    
        #[test]
        fn get_field_as_rmpv() {
            let s = S {
                x: 111,
                y: 2.22,
                s: "sssss".into(),
                v: vec!["v".into(), "vv".into(), "vvv".into()],
                r#struct: Nested {
                    a: "aaaaaa".into(),
                    b: 0xbbbbbb,
                    empty: Empty {},
                },
                ignored: serde_yaml::Value::default(),
            };
    
            //
            // Check `get_field_as_rmpv` error cases
            //
            let e = s.get_field_as_rmpv("a").unwrap_err();
            assert_eq!(
                e.to_string(),
                "unknown field `a`, expected one of `x`, `y`, `s`, `v`, `struct`"
            );
    
            let e = s.get_field_as_rmpv(".x").unwrap_err();
            assert_eq!(e.to_string(), "expected a field name before '.x'");
    
            let e = s.get_field_as_rmpv("&-*%?!").unwrap_err();
            assert_eq!(
                e.to_string(),
                "unknown field `&-*%?!`, expected one of `x`, `y`, `s`, `v`, `struct`"
            );
    
            let e = s.get_field_as_rmpv("x.foo.bar").unwrap_err();
            assert_eq!(e.to_string(), "field 'x' has no nested sub-fields");
    
            let e = s.get_field_as_rmpv("struct.foo").unwrap_err();
            assert_eq!(
                e.to_string(),
                "struct: unknown field `foo`, expected one of `a`, `b`, `empty`"
            );
    
            let e = s.get_field_as_rmpv("struct.a.bar").unwrap_err();
            assert_eq!(e.to_string(), "field 'struct.a' has no nested sub-fields");
    
            let e = s.get_field_as_rmpv("struct.a..").unwrap_err();
            assert_eq!(e.to_string(), "expected a field name after 'struct.a.'");
    
            let e = s.get_field_as_rmpv("x.").unwrap_err();
            assert_eq!(e.to_string(), "expected a field name after 'x.'");
    
            let e = s.get_field_as_rmpv("ignored").unwrap_err();
            assert_eq!(
                e.to_string(),
                "unknown field `ignored`, expected one of `x`, `y`, `s`, `v`, `struct`"
            );
            assert_eq!(s.ignored, serde_yaml::Value::default());
    
            let e = s.get_field_as_rmpv("struct.empty.foo").unwrap_err();
            assert_eq!(
                e.to_string(),
                "struct.empty: unknown field `foo`, there are no fields at all"
            );
    
            //
            // Check `get_field_as_rmpv` success cases
            //
            assert_eq!(s.get_field_as_rmpv("x").unwrap(), rmpv::Value::from(111));
            assert_eq!(s.get_field_as_rmpv("y").unwrap(), rmpv::Value::F32(2.22));
            assert_eq!(
                s.get_field_as_rmpv("s").unwrap(),
                rmpv::Value::from("sssss")
            );
            assert_eq!(
                s.get_field_as_rmpv("v").unwrap(),
                rmpv::Value::Array(vec![
                    rmpv::Value::from("v"),
                    rmpv::Value::from("vv"),
                    rmpv::Value::from("vvv"),
                ])
            );
    
            assert_eq!(
                s.get_field_as_rmpv("struct.a").unwrap(),
                rmpv::Value::from("aaaaaa")
            );
            assert_eq!(
                s.get_field_as_rmpv("struct.b").unwrap(),
                rmpv::Value::from(0xbbbbbb)
            );
            assert_eq!(
                s.get_field_as_rmpv("struct.empty").unwrap(),
                rmpv::Value::Map(vec![])
            );
    
            // We can also get the entire `struct` sub-field if we wanted:
            assert_eq!(
                s.get_field_as_rmpv("struct").unwrap(),
                rmpv::Value::Map(vec![
                    (rmpv::Value::from("a"), rmpv::Value::from("aaaaaa")),
                    (rmpv::Value::from("b"), rmpv::Value::from(0xbbbbbb)),
                    (rmpv::Value::from("empty"), rmpv::Value::Map(vec![])),
                ])
            );
        }
    
        #[test]
        fn nested_field_names() {
            assert_eq!(
                S::FIELD_INFOS,
                &[
                    FieldInfo {
                        name: "x",
                        nested_fields: &[]
                    },
                    FieldInfo {
                        name: "y",
                        nested_fields: &[]
                    },
                    FieldInfo {
                        name: "s",
                        nested_fields: &[]
                    },
                    FieldInfo {
                        name: "v",
                        nested_fields: &[]
                    },
                    FieldInfo {
                        name: "struct",
                        nested_fields: &[
                            FieldInfo {
                                name: "a",
                                nested_fields: &[]
                            },
                            FieldInfo {
                                name: "b",
                                nested_fields: &[]
                            },
                            FieldInfo {
                                name: "empty",
                                nested_fields: &[]
                            },
                        ]
                    },
                ],
            );
    
            assert_eq!(
                leaf_field_paths::<S>(),
                &["x", "y", "s", "v", "struct.a", "struct.b", "struct.empty"]
            );
    
            assert_eq!(leaf_field_paths::<Nested>(), &["a", "b", "empty"]);
        }
    
        #[test]
        fn get_field_default_value_as_rmpv() {
            let s = S {
                x: 999,
                y: 8.88,
                s: "S".into(),
                v: vec!["V0".into(), "V1".into(), "V2".into()],
                r#struct: Nested {
                    a: "self.a which was set explicitly".into(),
                    b: 0xb,
                    empty: Empty {},
                },
                ignored: serde_yaml::Value::default(),
            };
    
            //
            // Error cases are mostly covered in tests above
            //
            let e = s
                .get_field_default_value_as_rmpv("struct.no_such_field")
                .unwrap_err();
            assert_eq!(
                e.to_string(),
                "struct: unknown field `no_such_field`, expected one of `a`, `b`, `empty`"
            );
    
            let e = s.get_field_default_value_as_rmpv("ignored").unwrap_err();
            assert_eq!(
                e.to_string(),
                "unknown field `ignored`, expected one of `x`, `y`, `s`, `v`, `struct`"
            );
    
            //
            // Success cases
            //
            assert_eq!(s.get_field_default_value_as_rmpv("x").unwrap(), None);
            // Remember, it's `self.x * 0.5`
            assert_eq!(
                s.get_field_default_value_as_rmpv("y").unwrap(),
                Some(rmpv::Value::F32(499.5))
            );
    
            assert_eq!(
                s.get_field_default_value_as_rmpv("s").unwrap(),
                Some(rmpv::Value::from("this is a &str but it still works"))
            );
    
            assert_eq!(
                s.get_field_default_value_as_rmpv("v").unwrap(),
                Some(rmpv::Value::Array(vec![
                    rmpv::Value::from("this"),
                    rmpv::Value::from("also"),
                    rmpv::Value::from("works"),
                ]))
            );
    
            assert_eq!(
                s.get_field_default_value_as_rmpv("struct.a").unwrap(),
                Some(rmpv::Value::from("nested of course works"))
            );
    
            assert_eq!(
                s.get_field_default_value_as_rmpv("struct.b").unwrap(),
                Some(rmpv::Value::from(
                    "self.a which was set explicitly, but type safety is missing unfortunately"
                ))
            );
    
            assert_eq!(
                s.get_field_default_value_as_rmpv("struct.empty").unwrap(),
                None
            );
    
            // For some reason we even allow getting the default for the whole subsection.
            #[rustfmt::skip]
            assert_eq!(
                s.get_field_default_value_as_rmpv("struct").unwrap(),
                Some(rmpv::Value::Map(vec![
                    (rmpv::Value::from("a"), rmpv::Value::from("nested of course works")),
                    (rmpv::Value::from("b"), rmpv::Value::from("self.a which was set explicitly, but type safety is missing unfortunately")),
                ])),
            );
        }
    }