//! 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")),
            ])),
        );
    }
}