Select Git revision
Pipfile.lock
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")),
])),
);
}
}