From 0a1cdc5107b924497ed51fde9c921682a32060bb Mon Sep 17 00:00:00 2001 From: Lily Mara <6109875+lily-mara@users.noreply.github.com> Date: Sat, 31 Jul 2021 20:03:13 -0700 Subject: [PATCH] Add the nu-serde crate (#3878) * Add the nu-serde crate nu-serde is a crate that can be used to turn a value implementing `serde::Serialize` into a `nu-protocol::Value`. This has the potential to significantly simplify plugin authorship. This crate was the previously independent [serde-nu](https://github.com/lily-mara/serde-nu) but the nushell maintainers expressed an interest in having it added to the mainline nushell repository. * fixup! Add the nu-serde crate --- Cargo.lock | 33 ++ crates/nu-serde/Cargo.toml | 19 + crates/nu-serde/README.md | 44 ++ crates/nu-serde/src/lib.rs | 485 ++++++++++++++++++ ...test__it_serializes_return_value_list.snap | 81 +++ ...__test__it_works_with_complex_structs.snap | 173 +++++++ ...__test__it_works_with_lists_of_values.snap | 76 +++ ...__test__it_works_with_single_integers.snap | 21 + crates/nu-serde/src/test.rs | 50 ++ 9 files changed, 982 insertions(+) create mode 100644 crates/nu-serde/Cargo.toml create mode 100644 crates/nu-serde/README.md create mode 100644 crates/nu-serde/src/lib.rs create mode 100644 crates/nu-serde/src/snapshots/nu_serde__test__it_serializes_return_value_list.snap create mode 100644 crates/nu-serde/src/snapshots/nu_serde__test__it_works_with_complex_structs.snap create mode 100644 crates/nu-serde/src/snapshots/nu_serde__test__it_works_with_lists_of_values.snap create mode 100644 crates/nu-serde/src/snapshots/nu_serde__test__it_works_with_single_integers.snap create mode 100644 crates/nu-serde/src/test.rs diff --git a/Cargo.lock b/Cargo.lock index 3931a1185..ee15b302f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2509,6 +2509,21 @@ dependencies = [ "adler32", ] +[[package]] +name = "insta" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1b21a2971cea49ca4613c0e9fe8225ecaf5de64090fddc6002284726e9244" +dependencies = [ + "console", + "lazy_static 1.4.0", + "serde 1.0.126", + "serde_json", + "serde_yaml", + "similar", + "uuid", +] + [[package]] name = "instant" version = "0.1.9" @@ -3538,6 +3553,18 @@ dependencies = [ "toml", ] +[[package]] +name = "nu-serde" +version = "0.34.1" +dependencies = [ + "bigdecimal", + "insta", + "nu-protocol", + "nu-source", + "serde 1.0.126", + "thiserror", +] + [[package]] name = "nu-source" version = "0.34.1" @@ -5616,6 +5643,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c970da16e7c682fa90a261cf0724dee241c9f7831635ecc4e988ae8f3b505559" +[[package]] +name = "similar" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad1d488a557b235fc46dae55512ffbfc429d2482b08b4d9435ab07384ca8aec" + [[package]] name = "siphasher" version = "0.3.5" diff --git a/crates/nu-serde/Cargo.toml b/crates/nu-serde/Cargo.toml new file mode 100644 index 000000000..3a084e210 --- /dev/null +++ b/crates/nu-serde/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "nu-serde" +version = "0.34.1" +edition = "2018" +authors = ["The Nu Project Contributors"] +description = "Turn any value into a nu-protocol::Value with serde" +license = "MIT" +repository = "https://github.com/nushell/nushell" +documentation = "https://docs.rs/nu-serde" + +[dependencies] +bigdecimal = "0.2" +nu-protocol = { version = "0.34.1", path = "../nu-protocol" } +nu-source = { version = "0.34.1", path = "../nu-source" } +serde = "1" +thiserror = "1" + +[dev-dependencies] +insta = "1" diff --git a/crates/nu-serde/README.md b/crates/nu-serde/README.md new file mode 100644 index 000000000..75aca0121 --- /dev/null +++ b/crates/nu-serde/README.md @@ -0,0 +1,44 @@ +# `serde-nu` + +Convert any value implementing `serde::Serialize` into a +`nu_protocol::Value` using `nu_serde::to_value`. Compare the below manual +implemeentation and the one using `nu_serde`. + +```rust +use nu_protocol::{Dictionary, Primitive, UntaggedValue, Value}; +use nu_source::Tag; +use serde::Serialize; + +#[derive(Serialize)] +struct MyStruct { + index: usize, + name: String, +} + +fn manual(s: MyStruct, tag: Tag) -> Value { + let mut dict = Dictionary::default(); + dict.insert( + "index".into(), + Value { + value: UntaggedValue::Primitive(Primitive::Int(s.index as i64)), + tag: tag.clone(), + }, + ); + dict.insert( + "name".into(), + Value { + value: UntaggedValue::Primitive(Primitive::String(s.name)), + tag: tag.clone(), + }, + ); + + Value { + value: UntaggedValue::Row(dict), + tag, + } +} + +fn auto(s: &MyStruct, tag: Tag) -> Value { + nu_serde::to_value(s, tag).unwrap() +} +``` diff --git a/crates/nu-serde/src/lib.rs b/crates/nu-serde/src/lib.rs new file mode 100644 index 000000000..977d08001 --- /dev/null +++ b/crates/nu-serde/src/lib.rs @@ -0,0 +1,485 @@ +//! Convert any value implementing `serde::Serialize` into a +//! `nu_protocol::Value` using `nu_serde::to_value`. Compare the below manual +//! implemeentation and the one using `nu_serde`. +//! +//! ``` +//! use nu_protocol::{Dictionary, Primitive, UntaggedValue, Value}; +//! use nu_source::Tag; +//! use serde::Serialize; +//! +//! #[derive(Serialize)] +//! struct MyStruct { +//! index: usize, +//! name: String, +//! } +//! +//! fn manual(s: MyStruct, tag: Tag) -> Value { +//! let mut dict = Dictionary::default(); +//! dict.insert( +//! "index".into(), +//! Value { +//! value: UntaggedValue::Primitive(Primitive::Int(s.index as i64)), +//! tag: tag.clone(), +//! }, +//! ); +//! dict.insert( +//! "name".into(), +//! Value { +//! value: UntaggedValue::Primitive(Primitive::String(s.name)), +//! tag: tag.clone(), +//! }, +//! ); +//! +//! Value { +//! value: UntaggedValue::Row(dict), +//! tag, +//! } +//! } +//! +//! fn auto(s: &MyStruct, tag: Tag) -> Value { +//! nu_serde::to_value(s, tag).unwrap() +//! } +//! ``` + +use bigdecimal::{BigDecimal, FromPrimitive}; +use nu_protocol::value::dict::Dictionary; +use nu_protocol::{Primitive, ReturnSuccess, ReturnValue, UntaggedValue, Value}; +use nu_source::Tag; +use serde::Serialize; + +#[cfg(test)] +mod test; + +#[derive(Debug, thiserror::Error, Serialize)] +pub enum Error { + #[error("{0}")] + SerdeCustom(String), + + #[error("Expceted serializer to provide map implementation with a key before value")] + MapValueLackedKey, + + #[error("Expceted map key to be string, found {0:?}")] + InvalidMapKey(Value), + + #[error("Failed to convert f32 value into BigDecimal")] + F32BigDecimalError(f32), + + #[error("Failed to convert f64 value into BigDecimal")] + F64BigDecimalError(f64), +} + +impl serde::ser::Error for Error { + fn custom(msg: T) -> Self + where + T: std::fmt::Display, + { + Self::SerdeCustom(format!("{}", msg)) + } +} + +/// Convert any value into a `nu_protocol::Value` +pub fn to_value(value: T, tag: impl Into) -> Result +where + T: Serialize, +{ + value.serialize(&Serializer { tag: tag.into() }) +} + +/// Convenience function that takes an iterator over values and turns them into +/// a `Vec` (all successful). This is necessary for the return +/// signatures of most functions in the `nu_plugin::Plugin` trait. +pub fn to_success_return_values( + values: impl IntoIterator, + tag: impl Into, +) -> Result, Error> +where + T: Serialize, +{ + let tag = tag.into(); + + let mut out_values = Vec::new(); + + for value in values { + let value = to_value(&value, &tag)?; + + out_values.push(ReturnValue::Ok(ReturnSuccess::Value(value))); + } + + Ok(out_values) +} + +struct Serializer { + tag: Tag, +} + +struct SeqSerializer<'a> { + seq: Vec, + serializer: &'a Serializer, +} + +struct MapSerializer<'a> { + dict: Dictionary, + serializer: &'a Serializer, + current_key: Option, +} + +impl Serializer { + fn value(&self, untagged: UntaggedValue) -> Value { + Value { + value: untagged, + tag: self.tag.clone(), + } + } +} + +impl<'a> serde::ser::SerializeSeq for SeqSerializer<'a> { + type Ok = Value; + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: Serialize, + { + self.seq.push(value.serialize(self.serializer)?); + + Ok(()) + } + + fn end(self) -> Result { + Ok(self.serializer.value(UntaggedValue::Table(self.seq))) + } +} + +impl<'a> serde::ser::SerializeTuple for SeqSerializer<'a> { + type Ok = Value; + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: Serialize, + { + self.seq.push(value.serialize(self.serializer)?); + + Ok(()) + } + + fn end(self) -> Result { + Ok(self.serializer.value(UntaggedValue::Table(self.seq))) + } +} + +impl<'a> serde::ser::SerializeTupleStruct for SeqSerializer<'a> { + type Ok = Value; + type Error = Error; + + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + where + T: Serialize, + { + self.seq.push(value.serialize(self.serializer)?); + + Ok(()) + } + + fn end(self) -> Result { + Ok(self.serializer.value(UntaggedValue::Table(self.seq))) + } +} + +impl<'a> serde::ser::SerializeTupleVariant for SeqSerializer<'a> { + type Ok = Value; + + type Error = Error; + + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + where + T: Serialize, + { + self.seq.push(value.serialize(self.serializer)?); + + Ok(()) + } + + fn end(self) -> Result { + Ok(self.serializer.value(UntaggedValue::Table(self.seq))) + } +} + +impl<'a> serde::ser::SerializeMap for MapSerializer<'a> { + type Ok = Value; + type Error = Error; + + fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error> + where + T: Serialize, + { + let key_value = key.serialize(self.serializer)?; + + let key = match key_value.value { + UntaggedValue::Primitive(Primitive::String(s)) => s, + _ => return Err(Error::InvalidMapKey(key_value)), + }; + + self.current_key = Some(key); + + Ok(()) + } + + fn serialize_value(&mut self, value: &T) -> Result<(), Self::Error> + where + T: Serialize, + { + let key = self.current_key.take().ok_or(Error::MapValueLackedKey)?; + + self.dict.insert(key, value.serialize(self.serializer)?); + + Ok(()) + } + + fn end(self) -> Result { + Ok(self.serializer.value(UntaggedValue::Row(self.dict))) + } +} + +impl<'a> serde::ser::SerializeStruct for MapSerializer<'a> { + type Ok = Value; + + type Error = Error; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<(), Self::Error> + where + T: Serialize, + { + self.dict + .insert(key.to_owned(), value.serialize(self.serializer)?); + + Ok(()) + } + + fn end(self) -> Result { + Ok(self.serializer.value(UntaggedValue::Row(self.dict))) + } +} + +impl<'a> serde::ser::SerializeStructVariant for MapSerializer<'a> { + type Ok = Value; + + type Error = Error; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<(), Self::Error> + where + T: Serialize, + { + self.dict + .insert(key.to_owned(), value.serialize(self.serializer)?); + + Ok(()) + } + + fn end(self) -> Result { + Ok(self.serializer.value(UntaggedValue::Row(self.dict))) + } +} + +impl<'a> SeqSerializer<'a> { + fn new(serializer: &'a Serializer) -> Self { + Self { + seq: Vec::new(), + serializer, + } + } +} + +impl<'a> MapSerializer<'a> { + fn new(serializer: &'a Serializer) -> Self { + Self { + dict: Dictionary::default(), + current_key: None, + serializer, + } + } +} + +impl<'a> serde::Serializer for &'a Serializer { + type Ok = Value; + type Error = Error; + + type SerializeSeq = SeqSerializer<'a>; + type SerializeTuple = SeqSerializer<'a>; + type SerializeTupleStruct = SeqSerializer<'a>; + type SerializeTupleVariant = SeqSerializer<'a>; + type SerializeMap = MapSerializer<'a>; + type SerializeStruct = MapSerializer<'a>; + type SerializeStructVariant = MapSerializer<'a>; + + fn serialize_bool(self, v: bool) -> Result { + Ok(self.value(UntaggedValue::Primitive(Primitive::Boolean(v)))) + } + + fn serialize_i8(self, v: i8) -> Result { + Ok(self.value(UntaggedValue::Primitive(Primitive::Int(v as i64)))) + } + + fn serialize_i16(self, v: i16) -> Result { + Ok(self.value(UntaggedValue::Primitive(Primitive::Int(v as i64)))) + } + + fn serialize_i32(self, v: i32) -> Result { + Ok(self.value(UntaggedValue::Primitive(Primitive::Int(v as i64)))) + } + + fn serialize_i64(self, v: i64) -> Result { + Ok(self.value(UntaggedValue::Primitive(Primitive::Int(v as i64)))) + } + + fn serialize_u8(self, v: u8) -> Result { + Ok(self.value(UntaggedValue::Primitive(Primitive::Int(v as i64)))) + } + + fn serialize_u16(self, v: u16) -> Result { + Ok(self.value(UntaggedValue::Primitive(Primitive::Int(v as i64)))) + } + + fn serialize_u32(self, v: u32) -> Result { + Ok(self.value(UntaggedValue::Primitive(Primitive::Int(v as i64)))) + } + + fn serialize_u64(self, v: u64) -> Result { + Ok(self.value(UntaggedValue::Primitive(Primitive::BigInt(v.into())))) + } + + fn serialize_f32(self, v: f32) -> Result { + Ok(self.value(UntaggedValue::Primitive(Primitive::Decimal( + BigDecimal::from_f32(v).ok_or(Error::F32BigDecimalError(v))?, + )))) + } + + fn serialize_f64(self, v: f64) -> Result { + Ok(self.value(UntaggedValue::Primitive(Primitive::Decimal( + BigDecimal::from_f64(v).ok_or(Error::F64BigDecimalError(v))?, + )))) + } + + fn serialize_char(self, v: char) -> Result { + Ok(self.value(UntaggedValue::Primitive(Primitive::String(v.into())))) + } + + fn serialize_str(self, v: &str) -> Result { + Ok(self.value(UntaggedValue::Primitive(Primitive::String(v.into())))) + } + + fn serialize_bytes(self, v: &[u8]) -> Result { + Ok(self.value(UntaggedValue::Primitive(Primitive::Binary(v.into())))) + } + + fn serialize_none(self) -> Result { + Ok(self.value(UntaggedValue::Primitive(Primitive::Nothing))) + } + + fn serialize_some(self, value: &T) -> Result + where + T: Serialize, + { + value.serialize(self) + } + + fn serialize_unit(self) -> Result { + // TODO: is this OK? + Ok(self.value(UntaggedValue::Primitive(Primitive::Nothing))) + } + + fn serialize_unit_struct(self, _name: &'static str) -> Result { + // TODO: is this OK? + Ok(self.value(UntaggedValue::Primitive(Primitive::Nothing))) + } + + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + ) -> Result { + // TODO: is this OK? + Ok(self.value(UntaggedValue::Primitive(Primitive::Nothing))) + } + + fn serialize_newtype_struct( + self, + _name: &'static str, + value: &T, + ) -> Result + where + T: Serialize, + { + value.serialize(self) + } + + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + value: &T, + ) -> Result + where + T: Serialize, + { + value.serialize(self) + } + + fn serialize_seq(self, _len: Option) -> Result { + Ok(SeqSerializer::new(self)) + } + + fn serialize_tuple(self, _len: usize) -> Result { + Ok(SeqSerializer::new(self)) + } + + fn serialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result { + Ok(SeqSerializer::new(self)) + } + + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + Ok(SeqSerializer::new(self)) + } + + fn serialize_map(self, _len: Option) -> Result { + Ok(MapSerializer::new(self)) + } + + fn serialize_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result { + Ok(MapSerializer::new(self)) + } + + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, + ) -> Result { + Ok(MapSerializer::new(self)) + } +} diff --git a/crates/nu-serde/src/snapshots/nu_serde__test__it_serializes_return_value_list.snap b/crates/nu-serde/src/snapshots/nu_serde__test__it_serializes_return_value_list.snap new file mode 100644 index 000000000..72df45f59 --- /dev/null +++ b/crates/nu-serde/src/snapshots/nu_serde__test__it_serializes_return_value_list.snap @@ -0,0 +1,81 @@ +--- +source: src/test.rs +expression: "to_success_return_values(vec![4i32, 10, 8843234, 100])" + +--- +Ok( + [ + Ok( + Value( + Value { + value: Primitive( + Int( + 4, + ), + ), + tag: Tag { + anchor: None, + span: Span { + start: 0, + end: 0, + }, + }, + }, + ), + ), + Ok( + Value( + Value { + value: Primitive( + Int( + 10, + ), + ), + tag: Tag { + anchor: None, + span: Span { + start: 0, + end: 0, + }, + }, + }, + ), + ), + Ok( + Value( + Value { + value: Primitive( + Int( + 8843234, + ), + ), + tag: Tag { + anchor: None, + span: Span { + start: 0, + end: 0, + }, + }, + }, + ), + ), + Ok( + Value( + Value { + value: Primitive( + Int( + 100, + ), + ), + tag: Tag { + anchor: None, + span: Span { + start: 0, + end: 0, + }, + }, + }, + ), + ), + ], +) diff --git a/crates/nu-serde/src/snapshots/nu_serde__test__it_works_with_complex_structs.snap b/crates/nu-serde/src/snapshots/nu_serde__test__it_works_with_complex_structs.snap new file mode 100644 index 000000000..cb0be7011 --- /dev/null +++ b/crates/nu-serde/src/snapshots/nu_serde__test__it_works_with_complex_structs.snap @@ -0,0 +1,173 @@ +--- +source: src/test.rs +expression: "to_value(&Complex{index: -40, x: 32.8, y: 38.2, map,})" + +--- +Ok( + Value { + value: Row( + Dictionary { + entries: { + "index": Value { + value: Primitive( + Int( + -40, + ), + ), + tag: Tag { + anchor: None, + span: Span { + start: 0, + end: 0, + }, + }, + }, + "x": Value { + value: Primitive( + Decimal( + BigDecimal("32.80000"), + ), + ), + tag: Tag { + anchor: None, + span: Span { + start: 0, + end: 0, + }, + }, + }, + "y": Value { + value: Primitive( + Decimal( + BigDecimal("38.20000000000000"), + ), + ), + tag: Tag { + anchor: None, + span: Span { + start: 0, + end: 0, + }, + }, + }, + "map": Value { + value: Row( + Dictionary { + entries: { + "coconuts": Value { + value: Table( + [ + Value { + value: Primitive( + Int( + 4, + ), + ), + tag: Tag { + anchor: None, + span: Span { + start: 0, + end: 0, + }, + }, + }, + ], + ), + tag: Tag { + anchor: None, + span: Span { + start: 0, + end: 0, + }, + }, + }, + "mahi mahi": Value { + value: Table( + [], + ), + tag: Tag { + anchor: None, + span: Span { + start: 0, + end: 0, + }, + }, + }, + "tilapia": Value { + value: Table( + [ + Value { + value: Primitive( + Int( + 16, + ), + ), + tag: Tag { + anchor: None, + span: Span { + start: 0, + end: 0, + }, + }, + }, + Value { + value: Primitive( + Int( + 3, + ), + ), + tag: Tag { + anchor: None, + span: Span { + start: 0, + end: 0, + }, + }, + }, + Value { + value: Primitive( + Int( + 24, + ), + ), + tag: Tag { + anchor: None, + span: Span { + start: 0, + end: 0, + }, + }, + }, + ], + ), + tag: Tag { + anchor: None, + span: Span { + start: 0, + end: 0, + }, + }, + }, + }, + }, + ), + tag: Tag { + anchor: None, + span: Span { + start: 0, + end: 0, + }, + }, + }, + }, + }, + ), + tag: Tag { + anchor: None, + span: Span { + start: 0, + end: 0, + }, + }, + }, +) diff --git a/crates/nu-serde/src/snapshots/nu_serde__test__it_works_with_lists_of_values.snap b/crates/nu-serde/src/snapshots/nu_serde__test__it_works_with_lists_of_values.snap new file mode 100644 index 000000000..151e813bb --- /dev/null +++ b/crates/nu-serde/src/snapshots/nu_serde__test__it_works_with_lists_of_values.snap @@ -0,0 +1,76 @@ +--- +source: src/test.rs +expression: "to_value(&vec![4i32, 10, 8843234, 100])" + +--- +Ok( + Value { + value: Table( + [ + Value { + value: Primitive( + Int( + 4, + ), + ), + tag: Tag { + anchor: None, + span: Span { + start: 0, + end: 0, + }, + }, + }, + Value { + value: Primitive( + Int( + 10, + ), + ), + tag: Tag { + anchor: None, + span: Span { + start: 0, + end: 0, + }, + }, + }, + Value { + value: Primitive( + Int( + 8843234, + ), + ), + tag: Tag { + anchor: None, + span: Span { + start: 0, + end: 0, + }, + }, + }, + Value { + value: Primitive( + Int( + 100, + ), + ), + tag: Tag { + anchor: None, + span: Span { + start: 0, + end: 0, + }, + }, + }, + ], + ), + tag: Tag { + anchor: None, + span: Span { + start: 0, + end: 0, + }, + }, + }, +) diff --git a/crates/nu-serde/src/snapshots/nu_serde__test__it_works_with_single_integers.snap b/crates/nu-serde/src/snapshots/nu_serde__test__it_works_with_single_integers.snap new file mode 100644 index 000000000..0a8f0d160 --- /dev/null +++ b/crates/nu-serde/src/snapshots/nu_serde__test__it_works_with_single_integers.snap @@ -0,0 +1,21 @@ +--- +source: src/test.rs +expression: to_value(&4i32) + +--- +Ok( + Value { + value: Primitive( + Int( + 4, + ), + ), + tag: Tag { + anchor: None, + span: Span { + start: 0, + end: 0, + }, + }, + }, +) diff --git a/crates/nu-serde/src/test.rs b/crates/nu-serde/src/test.rs new file mode 100644 index 000000000..dea0263ee --- /dev/null +++ b/crates/nu-serde/src/test.rs @@ -0,0 +1,50 @@ +use crate::{to_success_return_values, to_value}; +use insta::assert_debug_snapshot; +use nu_source::Tag; +use serde::Serialize; +use std::collections::BTreeMap; + +#[test] +fn it_works_with_single_integers() { + assert_debug_snapshot!(to_value(&4i32, Tag::default())); +} + +#[test] +fn it_works_with_lists_of_values() { + assert_debug_snapshot!(to_value(&vec![4i32, 10, 8843234, 100], Tag::default())); +} + +#[test] +fn it_works_with_complex_structs() { + #[derive(Serialize, Debug)] + struct Complex { + index: i64, + x: f32, + y: f64, + map: BTreeMap>, + } + + let mut map = BTreeMap::new(); + + map.insert("coconuts".into(), vec![4]); + map.insert("tilapia".into(), vec![16, 3, 24]); + map.insert("mahi mahi".into(), vec![]); + + assert_debug_snapshot!(to_value( + &Complex { + index: -40, + x: 32.8, + y: 38.2, + map + }, + Tag::default() + )); +} + +#[test] +fn it_serializes_return_value_list() { + assert_debug_snapshot!(to_success_return_values( + vec![4i32, 10, 8843234, 100], + Tag::default() + )); +}