mirror of
https://github.com/nushell/nushell.git
synced 2025-04-03 14:10:41 +02:00
# Description Using derived `IntoValue` and `FromValue` implementations on structs with named fields currently produce `Value::Record`s where each key is the key of the Rust struct. For records like the `$nu` constant, that won't work as this record uses `kebab-case` for it's keys. To accomodate this, I upgraded the `#[nu_value(rename_all = "...")]` helper attribute to also work on structs with named fields which will rename the keys via the same case conversion as the enums already have. # User-Facing Changes Users of these macros may choose different key styles for their in `Value` representation. # Tests + Formatting I added the same test suite as enums already have and updated the traits documentation with more examples that also pass the doc test. # After Submitting I played around with the `$nu` constant but got stuck at the point that these keys are kebab-cased, with this, I can play around more with it.
589 lines
17 KiB
Rust
589 lines
17 KiB
Rust
use crate::{record, FromValue, IntoValue, Record, Span, Value};
|
|
use bytes::Bytes;
|
|
use std::collections::HashMap;
|
|
|
|
// Make nu_protocol available in this namespace, consumers of this crate will
|
|
// have this without such an export.
|
|
// The derive macro fully qualifies paths to "nu_protocol".
|
|
use crate as nu_protocol;
|
|
|
|
trait IntoTestValue {
|
|
fn into_test_value(self) -> Value;
|
|
}
|
|
|
|
impl<T> IntoTestValue for T
|
|
where
|
|
T: IntoValue,
|
|
{
|
|
fn into_test_value(self) -> Value {
|
|
self.into_value(Span::test_data())
|
|
}
|
|
}
|
|
|
|
#[derive(IntoValue, FromValue, Debug, PartialEq)]
|
|
struct NamedFieldsStruct<T>
|
|
where
|
|
T: IntoValue + FromValue,
|
|
{
|
|
array: [u16; 4],
|
|
bool: bool,
|
|
char: char,
|
|
f32: f32,
|
|
f64: f64,
|
|
i8: i8,
|
|
i16: i16,
|
|
i32: i32,
|
|
i64: i64,
|
|
isize: isize,
|
|
u16: u16,
|
|
u32: u32,
|
|
unit: (),
|
|
tuple: (u32, bool),
|
|
some: Option<u32>,
|
|
none: Option<u32>,
|
|
vec: Vec<T>,
|
|
string: String,
|
|
hashmap: HashMap<String, u32>,
|
|
nested: Nestee,
|
|
}
|
|
|
|
#[derive(IntoValue, FromValue, Debug, PartialEq)]
|
|
struct Nestee {
|
|
u32: u32,
|
|
some: Option<u32>,
|
|
none: Option<u32>,
|
|
}
|
|
|
|
impl NamedFieldsStruct<u32> {
|
|
fn make() -> Self {
|
|
Self {
|
|
array: [1, 2, 3, 4],
|
|
bool: true,
|
|
char: 'a',
|
|
f32: std::f32::consts::PI,
|
|
f64: std::f64::consts::E,
|
|
i8: 127,
|
|
i16: -32768,
|
|
i32: 2147483647,
|
|
i64: -9223372036854775808,
|
|
isize: 2,
|
|
u16: 65535,
|
|
u32: 4294967295,
|
|
unit: (),
|
|
tuple: (1, true),
|
|
some: Some(123),
|
|
none: None,
|
|
vec: vec![10, 20, 30],
|
|
string: "string".to_string(),
|
|
hashmap: HashMap::from_iter([("a".to_string(), 10), ("b".to_string(), 20)]),
|
|
nested: Nestee {
|
|
u32: 3,
|
|
some: Some(42),
|
|
none: None,
|
|
},
|
|
}
|
|
}
|
|
|
|
fn value() -> Value {
|
|
Value::test_record(record! {
|
|
"array" => Value::test_list(vec![
|
|
Value::test_int(1),
|
|
Value::test_int(2),
|
|
Value::test_int(3),
|
|
Value::test_int(4)
|
|
]),
|
|
"bool" => Value::test_bool(true),
|
|
"char" => Value::test_string('a'),
|
|
"f32" => Value::test_float(std::f32::consts::PI.into()),
|
|
"f64" => Value::test_float(std::f64::consts::E),
|
|
"i8" => Value::test_int(127),
|
|
"i16" => Value::test_int(-32768),
|
|
"i32" => Value::test_int(2147483647),
|
|
"i64" => Value::test_int(-9223372036854775808),
|
|
"isize" => Value::test_int(2),
|
|
"u16" => Value::test_int(65535),
|
|
"u32" => Value::test_int(4294967295),
|
|
"unit" => Value::test_nothing(),
|
|
"tuple" => Value::test_list(vec![
|
|
Value::test_int(1),
|
|
Value::test_bool(true)
|
|
]),
|
|
"some" => Value::test_int(123),
|
|
"none" => Value::test_nothing(),
|
|
"vec" => Value::test_list(vec![
|
|
Value::test_int(10),
|
|
Value::test_int(20),
|
|
Value::test_int(30)
|
|
]),
|
|
"string" => Value::test_string("string"),
|
|
"hashmap" => Value::test_record(record! {
|
|
"a" => Value::test_int(10),
|
|
"b" => Value::test_int(20)
|
|
}),
|
|
"nested" => Value::test_record(record! {
|
|
"u32" => Value::test_int(3),
|
|
"some" => Value::test_int(42),
|
|
"none" => Value::test_nothing(),
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn named_fields_struct_into_value() {
|
|
let expected = NamedFieldsStruct::value();
|
|
let actual = NamedFieldsStruct::make().into_test_value();
|
|
assert_eq!(expected, actual);
|
|
}
|
|
|
|
#[test]
|
|
fn named_fields_struct_from_value() {
|
|
let expected = NamedFieldsStruct::make();
|
|
let actual = NamedFieldsStruct::from_value(NamedFieldsStruct::value()).unwrap();
|
|
assert_eq!(expected, actual);
|
|
}
|
|
|
|
#[test]
|
|
fn named_fields_struct_roundtrip() {
|
|
let expected = NamedFieldsStruct::make();
|
|
let actual =
|
|
NamedFieldsStruct::from_value(NamedFieldsStruct::make().into_test_value()).unwrap();
|
|
assert_eq!(expected, actual);
|
|
|
|
let expected = NamedFieldsStruct::value();
|
|
let actual = NamedFieldsStruct::<u32>::from_value(NamedFieldsStruct::value())
|
|
.unwrap()
|
|
.into_test_value();
|
|
assert_eq!(expected, actual);
|
|
}
|
|
|
|
#[test]
|
|
fn named_fields_struct_missing_value() {
|
|
let value = Value::test_record(Record::new());
|
|
let res: Result<NamedFieldsStruct<u32>, _> = NamedFieldsStruct::from_value(value);
|
|
assert!(res.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn named_fields_struct_incorrect_type() {
|
|
// Should work for every type that is not a record.
|
|
let value = Value::test_nothing();
|
|
let res: Result<NamedFieldsStruct<u32>, _> = NamedFieldsStruct::from_value(value);
|
|
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
|
|
T: IntoValue + FromValue;
|
|
|
|
impl UnnamedFieldsStruct<f64> {
|
|
fn make() -> Self {
|
|
UnnamedFieldsStruct(420, "Hello, tuple!".to_string(), 33.33)
|
|
}
|
|
|
|
fn value() -> Value {
|
|
Value::test_list(vec![
|
|
Value::test_int(420),
|
|
Value::test_string("Hello, tuple!"),
|
|
Value::test_float(33.33),
|
|
])
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn unnamed_fields_struct_into_value() {
|
|
let expected = UnnamedFieldsStruct::value();
|
|
let actual = UnnamedFieldsStruct::make().into_test_value();
|
|
assert_eq!(expected, actual);
|
|
}
|
|
|
|
#[test]
|
|
fn unnamed_fields_struct_from_value() {
|
|
let expected = UnnamedFieldsStruct::make();
|
|
let value = UnnamedFieldsStruct::value();
|
|
let actual = UnnamedFieldsStruct::from_value(value).unwrap();
|
|
assert_eq!(expected, actual);
|
|
}
|
|
|
|
#[test]
|
|
fn unnamed_fields_struct_roundtrip() {
|
|
let expected = UnnamedFieldsStruct::make();
|
|
let actual =
|
|
UnnamedFieldsStruct::from_value(UnnamedFieldsStruct::make().into_test_value()).unwrap();
|
|
assert_eq!(expected, actual);
|
|
|
|
let expected = UnnamedFieldsStruct::value();
|
|
let actual = UnnamedFieldsStruct::<f64>::from_value(UnnamedFieldsStruct::value())
|
|
.unwrap()
|
|
.into_test_value();
|
|
assert_eq!(expected, actual);
|
|
}
|
|
|
|
#[test]
|
|
fn unnamed_fields_struct_missing_value() {
|
|
let value = Value::test_list(vec![]);
|
|
let res: Result<UnnamedFieldsStruct<f64>, _> = UnnamedFieldsStruct::from_value(value);
|
|
assert!(res.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn unnamed_fields_struct_incorrect_type() {
|
|
// Should work for every type that is not a record.
|
|
let value = Value::test_nothing();
|
|
let res: Result<UnnamedFieldsStruct<f64>, _> = UnnamedFieldsStruct::from_value(value);
|
|
assert!(res.is_err());
|
|
}
|
|
|
|
#[derive(IntoValue, FromValue, Debug, PartialEq)]
|
|
struct UnitStruct;
|
|
|
|
#[test]
|
|
fn unit_struct_into_value() {
|
|
let expected = Value::test_nothing();
|
|
let actual = UnitStruct.into_test_value();
|
|
assert_eq!(expected, actual);
|
|
}
|
|
|
|
#[test]
|
|
fn unit_struct_from_value() {
|
|
let expected = UnitStruct;
|
|
let actual = UnitStruct::from_value(Value::test_nothing()).unwrap();
|
|
assert_eq!(expected, actual);
|
|
}
|
|
|
|
#[test]
|
|
fn unit_struct_roundtrip() {
|
|
let expected = UnitStruct;
|
|
let actual = UnitStruct::from_value(UnitStruct.into_test_value()).unwrap();
|
|
assert_eq!(expected, actual);
|
|
|
|
let expected = Value::test_nothing();
|
|
let actual = UnitStruct::from_value(Value::test_nothing())
|
|
.unwrap()
|
|
.into_test_value();
|
|
assert_eq!(expected, actual);
|
|
}
|
|
|
|
#[derive(IntoValue, FromValue, Debug, PartialEq)]
|
|
enum Enum {
|
|
AlphaOne,
|
|
BetaTwo,
|
|
CharlieThree,
|
|
}
|
|
|
|
impl Enum {
|
|
fn make() -> [Self; 3] {
|
|
[Enum::AlphaOne, Enum::BetaTwo, Enum::CharlieThree]
|
|
}
|
|
|
|
fn value() -> Value {
|
|
Value::test_list(vec![
|
|
Value::test_string("alpha_one"),
|
|
Value::test_string("beta_two"),
|
|
Value::test_string("charlie_three"),
|
|
])
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn enum_into_value() {
|
|
let expected = Enum::value();
|
|
let actual = Enum::make().into_test_value();
|
|
assert_eq!(expected, actual);
|
|
}
|
|
|
|
#[test]
|
|
fn enum_from_value() {
|
|
let expected = Enum::make();
|
|
let actual = <[Enum; 3]>::from_value(Enum::value()).unwrap();
|
|
assert_eq!(expected, actual);
|
|
}
|
|
|
|
#[test]
|
|
fn enum_roundtrip() {
|
|
let expected = Enum::make();
|
|
let actual = <[Enum; 3]>::from_value(Enum::make().into_test_value()).unwrap();
|
|
assert_eq!(expected, actual);
|
|
|
|
let expected = Enum::value();
|
|
let actual = <[Enum; 3]>::from_value(Enum::value())
|
|
.unwrap()
|
|
.into_test_value();
|
|
assert_eq!(expected, actual);
|
|
}
|
|
|
|
#[test]
|
|
fn enum_unknown_variant() {
|
|
let value = Value::test_string("delta_four");
|
|
let res = Enum::from_value(value);
|
|
assert!(res.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn enum_incorrect_type() {
|
|
// Should work for every type that is not a record.
|
|
let value = Value::test_nothing();
|
|
let res = Enum::from_value(value);
|
|
assert!(res.is_err());
|
|
}
|
|
|
|
mod enum_rename_all {
|
|
use super::*;
|
|
use crate as nu_protocol;
|
|
|
|
// Generate the `Enum` from before but with all possible `rename_all` variants.
|
|
macro_rules! enum_rename_all {
|
|
($($ident:ident: $case:literal => [$a1:literal, $b2:literal, $c3:literal]),*) => {
|
|
$(
|
|
#[derive(Debug, PartialEq, IntoValue, FromValue)]
|
|
#[nu_value(rename_all = $case)]
|
|
enum $ident {
|
|
AlphaOne,
|
|
BetaTwo,
|
|
CharlieThree
|
|
}
|
|
|
|
impl $ident {
|
|
fn make() -> [Self; 3] {
|
|
[Self::AlphaOne, Self::BetaTwo, Self::CharlieThree]
|
|
}
|
|
|
|
fn value() -> Value {
|
|
Value::test_list(vec![
|
|
Value::test_string($a1),
|
|
Value::test_string($b2),
|
|
Value::test_string($c3),
|
|
])
|
|
}
|
|
}
|
|
)*
|
|
|
|
#[test]
|
|
fn into_value() {$({
|
|
let expected = $ident::value();
|
|
let actual = $ident::make().into_test_value();
|
|
assert_eq!(expected, actual);
|
|
})*}
|
|
|
|
#[test]
|
|
fn from_value() {$({
|
|
let expected = $ident::make();
|
|
let actual = <[$ident; 3]>::from_value($ident::value()).unwrap();
|
|
assert_eq!(expected, actual);
|
|
})*}
|
|
}
|
|
}
|
|
|
|
enum_rename_all! {
|
|
Upper: "UPPER CASE" => ["ALPHA ONE", "BETA TWO", "CHARLIE THREE"],
|
|
Lower: "lower case" => ["alpha one", "beta two", "charlie three"],
|
|
Title: "Title Case" => ["Alpha One", "Beta Two", "Charlie Three"],
|
|
Camel: "camelCase" => ["alphaOne", "betaTwo", "charlieThree"],
|
|
Pascal: "PascalCase" => ["AlphaOne", "BetaTwo", "CharlieThree"],
|
|
Snake: "snake_case" => ["alpha_one", "beta_two", "charlie_three"],
|
|
UpperSnake: "UPPER_SNAKE_CASE" => ["ALPHA_ONE", "BETA_TWO", "CHARLIE_THREE"],
|
|
Kebab: "kebab-case" => ["alpha-one", "beta-two", "charlie-three"],
|
|
Cobol: "COBOL-CASE" => ["ALPHA-ONE", "BETA-TWO", "CHARLIE-THREE"],
|
|
Train: "Train-Case" => ["Alpha-One", "Beta-Two", "Charlie-Three"],
|
|
Flat: "flatcase" => ["alphaone", "betatwo", "charliethree"],
|
|
UpperFlat: "UPPERFLATCASE" => ["ALPHAONE", "BETATWO", "CHARLIETHREE"]
|
|
}
|
|
}
|
|
|
|
mod named_fields_struct_rename_all {
|
|
use super::*;
|
|
use crate as nu_protocol;
|
|
|
|
macro_rules! named_fields_struct_rename_all {
|
|
($($ident:ident: $case:literal => [$a1:literal, $b2:literal, $c3:literal]),*) => {
|
|
$(
|
|
#[derive(Debug, PartialEq, IntoValue, FromValue)]
|
|
#[nu_value(rename_all = $case)]
|
|
struct $ident {
|
|
alpha_one: (),
|
|
beta_two: (),
|
|
charlie_three: (),
|
|
}
|
|
|
|
impl $ident {
|
|
fn make() -> Self {
|
|
Self {
|
|
alpha_one: (),
|
|
beta_two: (),
|
|
charlie_three: (),
|
|
}
|
|
}
|
|
|
|
fn value() -> Value {
|
|
Value::test_record(record! {
|
|
$a1 => Value::test_nothing(),
|
|
$b2 => Value::test_nothing(),
|
|
$c3 => Value::test_nothing(),
|
|
})
|
|
}
|
|
}
|
|
)*
|
|
|
|
#[test]
|
|
fn into_value() {$({
|
|
let expected = $ident::value();
|
|
let actual = $ident::make().into_test_value();
|
|
assert_eq!(expected, actual);
|
|
})*}
|
|
|
|
#[test]
|
|
fn from_value() {$({
|
|
let expected = $ident::make();
|
|
let actual = $ident::from_value($ident::value()).unwrap();
|
|
assert_eq!(expected, actual);
|
|
})*}
|
|
}
|
|
}
|
|
|
|
named_fields_struct_rename_all! {
|
|
Upper: "UPPER CASE" => ["ALPHA ONE", "BETA TWO", "CHARLIE THREE"],
|
|
Lower: "lower case" => ["alpha one", "beta two", "charlie three"],
|
|
Title: "Title Case" => ["Alpha One", "Beta Two", "Charlie Three"],
|
|
Camel: "camelCase" => ["alphaOne", "betaTwo", "charlieThree"],
|
|
Pascal: "PascalCase" => ["AlphaOne", "BetaTwo", "CharlieThree"],
|
|
Snake: "snake_case" => ["alpha_one", "beta_two", "charlie_three"],
|
|
UpperSnake: "UPPER_SNAKE_CASE" => ["ALPHA_ONE", "BETA_TWO", "CHARLIE_THREE"],
|
|
Kebab: "kebab-case" => ["alpha-one", "beta-two", "charlie-three"],
|
|
Cobol: "COBOL-CASE" => ["ALPHA-ONE", "BETA-TWO", "CHARLIE-THREE"],
|
|
Train: "Train-Case" => ["Alpha-One", "Beta-Two", "Charlie-Three"],
|
|
Flat: "flatcase" => ["alphaone", "betatwo", "charliethree"],
|
|
UpperFlat: "UPPERFLATCASE" => ["ALPHAONE", "BETATWO", "CHARLIETHREE"]
|
|
}
|
|
}
|
|
|
|
#[derive(IntoValue, FromValue, Debug, PartialEq)]
|
|
struct ByteContainer {
|
|
vec: Vec<u8>,
|
|
bytes: Bytes,
|
|
}
|
|
|
|
impl ByteContainer {
|
|
fn make() -> Self {
|
|
ByteContainer {
|
|
vec: vec![1, 2, 3],
|
|
bytes: Bytes::from_static(&[4, 5, 6]),
|
|
}
|
|
}
|
|
|
|
fn value() -> Value {
|
|
Value::test_record(record! {
|
|
"vec" => Value::test_list(vec![
|
|
Value::test_int(1),
|
|
Value::test_int(2),
|
|
Value::test_int(3),
|
|
]),
|
|
"bytes" => Value::test_binary(vec![4, 5, 6]),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn bytes_into_value() {
|
|
let expected = ByteContainer::value();
|
|
let actual = ByteContainer::make().into_test_value();
|
|
assert_eq!(expected, actual);
|
|
}
|
|
|
|
#[test]
|
|
fn bytes_from_value() {
|
|
let expected = ByteContainer::make();
|
|
let actual = ByteContainer::from_value(ByteContainer::value()).unwrap();
|
|
assert_eq!(expected, actual);
|
|
}
|
|
|
|
#[test]
|
|
fn bytes_roundtrip() {
|
|
let expected = ByteContainer::make();
|
|
let actual = ByteContainer::from_value(ByteContainer::make().into_test_value()).unwrap();
|
|
assert_eq!(expected, actual);
|
|
|
|
let expected = ByteContainer::value();
|
|
let actual = ByteContainer::from_value(ByteContainer::value())
|
|
.unwrap()
|
|
.into_test_value();
|
|
assert_eq!(expected, actual);
|
|
}
|
|
|
|
#[test]
|
|
fn struct_type_name_attr() {
|
|
#[derive(FromValue, Debug)]
|
|
#[nu_value(type_name = "struct")]
|
|
struct TypeNameStruct;
|
|
|
|
assert_eq!(
|
|
TypeNameStruct::expected_type().to_string().as_str(),
|
|
"struct"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn enum_type_name_attr() {
|
|
#[derive(FromValue, Debug)]
|
|
#[nu_value(type_name = "enum")]
|
|
struct TypeNameEnum;
|
|
|
|
assert_eq!(TypeNameEnum::expected_type().to_string().as_str(), "enum");
|
|
}
|