//! 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::config::SbroadType;
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 [`RmpvNamedSerializer`].
    ///
    /// 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));
    /// ```
    ///
    /// [`RmpvNamedSerializer`]: crate::to_rmpv_named::RmpvNamedSerializer
    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>;

    /// Get `SbroadType` which was specified via the `#[introspection(sbroad_type = expr)]` attribute.
    ///
    /// Returns `Ok(None)` if `sbroad_type` attribute wasn't provided for
    /// given field.
    ///
    /// Note that the value is not checked neither against the field's type nor
    /// against value specified in `config_default` attribute.
    /// So it's the user's responsibility to make sure the types are consistent.
    ///
    /// The `expr` in `#[introspection(sbroad_type = expr)]` must be of type
    /// [`SbroadType`] otherwise your code will not compile.
    ///
    /// # Examples:
    /// ```
    /// use picodata::introspection::Introspection;
    /// use picodata::config::SbroadType;
    ///
    /// #[derive(Introspection, Default)]
    /// #[introspection(crate = picodata)]
    /// struct MyStruct {
    ///     #[introspection(sbroad_type = SbroadType::Integer)]
    ///     number: i32,
    ///     #[introspection(sbroad_type = SbroadType::String)]
    ///     text: String,
    ///     type_wasnt_specified: bool,
    /// }
    ///
    /// assert_eq!(MyStruct::get_sbroad_type_of_field("number").unwrap(), Some(SbroadType::Integer));
    ///
    /// assert_eq!(MyStruct::get_sbroad_type_of_field("text").unwrap(), Some(SbroadType::String));
    ///
    /// assert_eq!(MyStruct::get_sbroad_type_of_field("type_wasnt_specified").unwrap(), None);
    /// ```
    fn get_sbroad_type_of_field(path: &str) -> Result<Option<SbroadType>, 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 scope: Option<&'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)?;
        write!(f, "{:?}", self.scope)?;
        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);
            }
        }
    }
}

/// Helpers and re-exports that exist solely for use by the `Introspection` derive macro.
pub mod private {
    pub use crate::to_rmpv_named::RmpvNamedSerializer;
    /// A public reimport for use in the derive macro.
    pub use rmpv::Value as RmpvValue;

    pub fn make_yaml_deserializer(
        value: &str,
    ) -> impl serde::Deserializer<'_, Error = serde_yaml::Error> {
        serde_yaml::Deserializer::from_str(value)
    }

    pub fn make_rpmv_deserializer(
        value: &rmpv::Value,
    ) -> impl serde::Deserializer<'_, Error = rmpv::ext::Error> {
        value.as_ref()
    }
}

#[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)]
//         #[introspection(sbroad_type = SbroadType::Double)]
//         y: f32,
//         #[introspection(config_default = "this is a &str but it still works")]
//         #[introspection(sbroad_type = SbroadType::String)]
//         s: String,
//         #[introspection(config_default = &["this", "also", "works"])]
//         #[introspection(sbroad_type = SbroadType::Array)]
//         #[introspection(scope = tier)]
//         v: Vec<String>,
//         #[introspection(nested)]
//         #[introspection(sbroad_type = SbroadType::Map)]
//         r#struct: Nested,

//         #[introspection(ignore)]
//         ignored: serde_yaml::Value,
//     }

//     #[derive(Default, Debug, Introspection, PartialEq)]
//     struct Nested {
//         #[introspection(config_default = "nested of course works")]
//         #[introspection(sbroad_type = SbroadType::String)]
//         a: String,
//         #[introspection(config_default = format!("{}, but type safety is missing unfortunately", self.a))]
//         #[introspection(scope = tier)]
//         b: i64,
//         #[introspection(nested)]
//         empty: Empty,
//     }

//     #[derive(Default, Debug, Introspection, PartialEq)]
//     struct OptionalS {
//         #[introspection(config_default = "42.0")]
//         c: Option<f32>,
//     }

//     #[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(),
//         };
//         let optional_s = OptionalS { c: None };

//         //
//         // Check `get_field_as_rmpv` error cases
//         //
//         let e = optional_s.get_field_as_rmpv("c").unwrap();
//         assert_eq!(e, rmpv::Value::Nil);

//         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",
//                     scope: None,
//                     nested_fields: &[]
//                 },
//                 FieldInfo {
//                     name: "y",
//                     scope: None,
//                     nested_fields: &[]
//                 },
//                 FieldInfo {
//                     name: "s",
//                     scope: None,
//                     nested_fields: &[]
//                 },
//                 FieldInfo {
//                     name: "v",
//                     scope: Some("tier"),
//                     nested_fields: &[]
//                 },
//                 FieldInfo {
//                     name: "struct",
//                     scope: None,
//                     nested_fields: &[
//                         FieldInfo {
//                             name: "a",
//                             scope: None,
//                             nested_fields: &[]
//                         },
//                         FieldInfo {
//                             name: "b",
//                             scope: Some("tier"),
//                             nested_fields: &[]
//                         },
//                         FieldInfo {
//                             name: "empty",
//                             scope: None,
//                             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")),
//             ])),
//         );
//     }

//     #[test]
//     fn get_sbroad_type_of_field() {
//         //
//         // Error cases are mostly covered in tests above
//         //
//         let e = S::get_sbroad_type_of_field("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_sbroad_type_of_field("ignored").unwrap_err();
//         assert_eq!(
//             e.to_string(),
//             "unknown field `ignored`, expected one of `x`, `y`, `s`, `v`, `struct`"
//         );

//         //
//         // Success cases
//         //
//         // We didn't specify type for `x` so it's `None`.
//         assert_eq!(S::get_sbroad_type_of_field("x").unwrap(), None);
//         // Remember, it's `self.x * 0.5`
//         assert_eq!(
//             S::get_sbroad_type_of_field("y").unwrap(),
//             Some(SbroadType::Double)
//         );

//         assert_eq!(
//             S::get_sbroad_type_of_field("s").unwrap(),
//             Some(SbroadType::String)
//         );

//         assert_eq!(
//             S::get_sbroad_type_of_field("v").unwrap(),
//             Some(SbroadType::Array)
//         );

//         // Type of whole subsection also works, if specified.
//         assert_eq!(
//             S::get_sbroad_type_of_field("struct").unwrap(),
//             Some(SbroadType::Map),
//         );

//         assert_eq!(
//             S::get_sbroad_type_of_field("struct.a").unwrap(),
//             Some(SbroadType::String)
//         );

//         // Didn't specify
//         assert_eq!(S::get_sbroad_type_of_field("struct.b").unwrap(), None);

//         // Type of nested subsection wasn't specified in this case.
//         assert_eq!(S::get_sbroad_type_of_field("struct.empty").unwrap(), None);
//     }
// }
