Allow missing fields in derived FromValue::from_value calls (#13206)

# Description
In #13031 I added the derive macros for `FromValue` and `IntoValue`. In
that implementation, in particular for structs with named fields, it was
not possible to omit fields while loading them from a value, when the
field is an `Option`. This PR adds extra handling for this behavior, so
if a field is an `Option` and that field is missing in the `Value`, then
the field becomes `None`. This behavior is also tested in
`nu_protocol::value::test_derive::missing_options`.

# User-Facing Changes
When using structs for options or similar, users can now just emit
fields in the record and the derive `from_value` method will be able to
understand this, if the struct has an `Option` type for that field.

# Tests + Formatting
- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting
A showcase for this feature would be great, I tried to use the current
derive macro in a plugin of mine for a config but without this addition,
they are annoying to use. So, when this is done, I would add an example
for such plugin configs that may be loaded via `FromValue`.
This commit is contained in:
Piepmatz
2024-06-22 22:31:09 +02:00
committed by GitHub
parent b6bdadbc6f
commit 9b7f899410
2 changed files with 103 additions and 19 deletions

View File

@ -171,6 +171,62 @@ fn named_fields_struct_incorrect_type() {
assert!(res.is_err());
}
#[derive(IntoValue, FromValue, Debug, PartialEq, Default)]
struct ALotOfOptions {
required: bool,
float: Option<f64>,
int: Option<i64>,
value: Option<Value>,
nested: Option<Nestee>,
}
#[test]
fn missing_options() {
let value = Value::test_record(Record::new());
let res: Result<ALotOfOptions, _> = ALotOfOptions::from_value(value);
assert!(res.is_err());
let value = Value::test_record(record! {"required" => Value::test_bool(true)});
let expected = ALotOfOptions {
required: true,
..Default::default()
};
let actual = ALotOfOptions::from_value(value).unwrap();
assert_eq!(expected, actual);
let value = Value::test_record(record! {
"required" => Value::test_bool(true),
"float" => Value::test_float(std::f64::consts::PI),
});
let expected = ALotOfOptions {
required: true,
float: Some(std::f64::consts::PI),
..Default::default()
};
let actual = ALotOfOptions::from_value(value).unwrap();
assert_eq!(expected, actual);
let value = Value::test_record(record! {
"required" => Value::test_bool(true),
"int" => Value::test_int(12),
"nested" => Value::test_record(record! {
"u32" => Value::test_int(34),
}),
});
let expected = ALotOfOptions {
required: true,
int: Some(12),
nested: Some(Nestee {
u32: 34,
some: None,
none: None,
}),
..Default::default()
};
let actual = ALotOfOptions::from_value(value).unwrap();
assert_eq!(expected, actual);
}
#[derive(IntoValue, FromValue, Debug, PartialEq)]
struct UnnamedFieldsStruct<T>(u32, String, T)
where