diff --git a/Cargo.lock b/Cargo.lock index e2395e5..4af4881 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -316,6 +316,11 @@ dependencies = [ "pest", "pest_derive", "ryu", + "serde", + "serde_core", + "serde_json", + "serde_yaml_ng", + "toml", "unicode-general-category", ] @@ -1092,7 +1097,6 @@ dependencies = [ "chrono", "duper", "ipnet", - "miette", "pest", "regex", "rust_decimal", @@ -1126,6 +1130,28 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_spanned" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_yaml_ng" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4db627b98b36d4203a7b458cf3573730f2bb591b28871d916dfa9efabfd41f" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha2" version = "0.10.9" @@ -1251,6 +1277,21 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "toml" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + [[package]] name = "toml_datetime" version = "0.7.3" @@ -1281,6 +1322,12 @@ dependencies = [ "winnow", ] +[[package]] +name = "toml_writer" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" + [[package]] name = "tower" version = "0.5.2" @@ -1355,6 +1402,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "uuid" version = "1.18.1" diff --git a/Cargo.toml b/Cargo.toml index e208438..cfa7827 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,6 @@ members = [ "serde_duper", "serde_duper_macros", ] + +[profile.release] +lto = true diff --git a/duper-js-wasm/Cargo.toml b/duper-js-wasm/Cargo.toml index f102b4b..8545b17 100644 --- a/duper-js-wasm/Cargo.toml +++ b/duper-js-wasm/Cargo.toml @@ -20,6 +20,3 @@ duper = { path = "../duper", version = "0.2.0" } js-sys = "0.3.81" miette = "7.6.0" wasm-bindgen = "0.2.104" - -[profile.release] -lto = true diff --git a/duper-js-wasm/src/lib.rs b/duper-js-wasm/src/lib.rs index c544460..680bb13 100644 --- a/duper-js-wasm/src/lib.rs +++ b/duper-js-wasm/src/lib.rs @@ -47,10 +47,8 @@ pub fn stringify(value: &JsValue, options: Option) -> Result< StringifyOptions { indent: if let Some(indent) = indent.as_f64() { Some((0..indent as u32).map(|_| ' ').collect()) - } else if let Some(indent) = indent.as_string() { - Some(indent) } else { - None + indent.as_string() }, strip_identifiers: strip_identifiers.is_truthy(), } diff --git a/duper-js-wasm/src/ser.rs b/duper-js-wasm/src/ser.rs index 35547cf..a841637 100644 --- a/duper-js-wasm/src/ser.rs +++ b/duper-js-wasm/src/ser.rs @@ -16,7 +16,7 @@ pub(crate) fn serialize_jsvalue(value: &JsValue) -> Result, if identifier.is_string() { identifier .as_string() - .map(|identifier| DuperIdentifier::try_from(identifier)) + .map(DuperIdentifier::try_from) .transpose() .map_err(|e| JsValue::from_str(&e.to_string()))? } else { @@ -127,7 +127,7 @@ fn serialize_array( value: &JsValue, identifier: Option>, ) -> Result, JsValue> { - let iterator = try_iter(value)?.ok_or_else(|| "cannot iterate over array")?; + let iterator = try_iter(value)?.ok_or("cannot iterate over array")?; let elements: Result, JsValue> = iterator.map(|value| serialize_jsvalue(&value?)).collect(); Ok(DuperValue { @@ -140,7 +140,7 @@ fn serialize_tuple( value: &JsValue, identifier: Option>, ) -> Result, JsValue> { - let iterator = try_iter(value)?.ok_or_else(|| "cannot iterate over tuple")?; + let iterator = try_iter(value)?.ok_or("cannot iterate over tuple")?; let elements: Result, JsValue> = iterator.map(|value| serialize_jsvalue(&value?)).collect(); Ok(DuperValue { diff --git a/duper-python/Cargo.toml b/duper-python/Cargo.toml index 55ea2ef..eb4b22a 100644 --- a/duper-python/Cargo.toml +++ b/duper-python/Cargo.toml @@ -24,6 +24,3 @@ pyo3 = { version = "0.27.1", features = [ "extension-module", "generate-import-lib", ] } - -[profile.release] -lto = true diff --git a/duper/Cargo.toml b/duper/Cargo.toml index 5bd4877..cf439ea 100644 --- a/duper/Cargo.toml +++ b/duper/Cargo.toml @@ -15,14 +15,20 @@ keywords = ["duper", "parser", "serialization"] [features] default = [] ansi = ["dep:owo-colors"] +serde = ["dep:serde_core"] [dependencies] -owo-colors = { version = "4.2.3", optional = true } +owo-colors = { version = "4", optional = true } +miette = "7" pest = { version = "2.8.3", features = ["miette-error"] } pest_derive = "2.8.3" -ryu = "1.0.20" -unicode-general-category = "1.1.0" +ryu = "1" +serde_core = { version = "1", optional = true } +unicode-general-category = "1" [dev-dependencies] insta = "1.43.2" -miette = "7.6.0" +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.145" +serde_yaml_ng = "0.10.0" +toml = "0.9.8" diff --git a/duper/src/ast.rs b/duper/src/ast.rs index 691c92b..6b10800 100644 --- a/duper/src/ast.rs +++ b/duper/src/ast.rs @@ -188,6 +188,16 @@ impl<'a> TryFrom> for DuperIdentifier<'a> { } } +impl<'a> TryFrom<&'a str> for DuperIdentifier<'a> { + type Error = DuperIdentifierTryFromError<'a>; + + /// Create a valid identifier from the provided `&str`, returning + /// an error if there are invalid characters. + fn try_from(value: &'a str) -> Result { + Self::try_from(Cow::Borrowed(value)) + } +} + impl TryFrom for DuperIdentifier<'static> { type Error = DuperIdentifierTryFromError<'static>; @@ -230,6 +240,12 @@ impl<'a> From> for DuperKey<'a> { } } +impl<'a> From<&'a str> for DuperKey<'a> { + fn from(value: &'a str) -> Self { + Self(Cow::Borrowed(value)) + } +} + impl From for DuperKey<'static> { fn from(value: String) -> Self { Self(Cow::Owned(value)) @@ -422,6 +438,12 @@ impl<'a> From> for DuperString<'a> { } } +impl<'a> From<&'a str> for DuperString<'a> { + fn from(value: &'a str) -> Self { + Self(Cow::Borrowed(value)) + } +} + impl From for DuperString<'static> { fn from(value: String) -> Self { Self(Cow::Owned(value)) @@ -447,6 +469,12 @@ impl<'a> From> for DuperBytes<'a> { } } +impl<'a> From<&'a [u8]> for DuperBytes<'a> { + fn from(value: &'a [u8]) -> Self { + Self(Cow::Borrowed(value)) + } +} + impl From> for DuperBytes<'static> { fn from(value: Vec) -> Self { Self(Cow::Owned(value)) diff --git a/duper/src/lib.rs b/duper/src/lib.rs index 9544f9e..01dfed5 100644 --- a/duper/src/lib.rs +++ b/duper/src/lib.rs @@ -40,6 +40,8 @@ mod builder; mod escape; mod format; mod parser; +#[cfg(feature = "serde")] +pub mod serde; pub mod visitor; pub use ast::{ diff --git a/serde_duper/src/de.rs b/duper/src/serde/de.rs similarity index 98% rename from serde_duper/src/de.rs rename to duper/src/serde/de.rs index 906ece3..5c3e20d 100644 --- a/serde_duper/src/de.rs +++ b/duper/src/serde/de.rs @@ -1,14 +1,13 @@ use std::borrow::Cow; -use duper::{DuperArray, DuperKey}; -use duper::{DuperInner, DuperParser, DuperValue}; +use crate::{DuperArray, DuperInner, DuperKey, DuperParser, DuperValue}; use serde_core::{ Deserialize, de::{self, DeserializeSeed, IntoDeserializer, Visitor}, forward_to_deserialize_any, }; -use crate::Error; +use super::error::Error; /// Implementation of a deserializer from a [`DuperValue`]. pub struct Deserializer<'de> { @@ -50,7 +49,7 @@ impl<'de> Deserializer<'de> { /// location: City("Menlo Park, CA"), /// })"#; /// -/// let u: User = serde_duper::from_string(j).unwrap(); +/// let u: User = duper::serde::de::from_string(j).unwrap(); /// println!("{:#?}", u); /// ``` /// @@ -79,7 +78,7 @@ where /// ``` /// use std::borrow::Cow; /// use serde::Deserialize; -/// use serde_duper::{ +/// use duper::{ /// DuperBytes, DuperIdentifier, DuperInner, DuperKey, DuperObject, /// DuperString, DuperValue, /// }; @@ -117,7 +116,7 @@ where /// ]).unwrap()), /// }; /// -/// let u: User = serde_duper::from_value(d).unwrap(); +/// let u: User = duper::serde::de::from_value(d).unwrap(); /// println!("{:#?}", u); /// ``` /// diff --git a/serde_duper/src/error.rs b/duper/src/serde/error.rs similarity index 93% rename from serde_duper/src/error.rs rename to duper/src/serde/error.rs index f52b6c9..6cc5234 100644 --- a/serde_duper/src/error.rs +++ b/duper/src/serde/error.rs @@ -1,6 +1,6 @@ use std::fmt::{self, Display}; -use duper::{DuperIdentifierTryFromError, DuperObjectTryFromError}; +use crate::{DuperIdentifierTryFromError, DuperObjectTryFromError}; /// The kinds of errors that can happen during serialization and deserialization. #[derive(Clone)] @@ -9,7 +9,7 @@ pub enum ErrorKind { /// /// This error implements `.to_miette()`, in order to allow generation of a /// `miette` `Diagnostic`. - ParseError(Box>), + ParseError(Box>), /// Serialization failed with an unspecified error. SerializationError, /// Deserialization failed with the given reason. @@ -111,8 +111,8 @@ impl From for Error { } } -impl From>> for Error { - fn from(value: Box>) -> Self { +impl From>> for Error { + fn from(value: Box>) -> Self { let message = value.variant.message().into_owned(); Self::new(ErrorKind::ParseError(value), message) } diff --git a/duper/src/serde/meta.rs b/duper/src/serde/meta.rs new file mode 100644 index 0000000..b05a792 --- /dev/null +++ b/duper/src/serde/meta.rs @@ -0,0 +1,809 @@ +use std::{borrow::Cow, fmt::Display}; + +use serde_core::{ + Deserializer, Serialize, + de::{Deserialize, Error, MapAccess, SeqAccess, Visitor}, + ser::{SerializeMap, SerializeSeq, SerializeStruct, SerializeTuple}, +}; + +use crate::{ + DuperArray, DuperBytes, DuperIdentifier, DuperInner, DuperKey, DuperObject, + DuperObjectTryFromError, DuperString, DuperTuple, DuperValue, +}; + +impl<'a> DuperValue<'a> { + /// A function that serializes the Duper value into a lossless struct + /// containing the `identifier`, `inner`, and `type` fields. + pub fn serialize_meta(&self, serializer: S) -> Result + where + S: serde_core::Serializer, + { + let mut state = serializer.serialize_struct("DuperValue", 3)?; + state.serialize_field("identifier", &self.identifier)?; + match &self.inner { + DuperInner::Object(object) => { + state.serialize_field("inner", &SerDuperObject(object))?; + state.serialize_field("type", "object")?; + } + DuperInner::Array(array) => { + state.serialize_field("inner", &SerDuperArray(array))?; + state.serialize_field("type", "array")?; + } + DuperInner::Tuple(tuple) => { + state.serialize_field("inner", &SerDuperTuple(tuple))?; + state.serialize_field("type", "tuple")?; + } + DuperInner::String(_) => { + state.serialize_field("inner", &self.inner)?; + state.serialize_field("type", "string")?; + } + DuperInner::Bytes(_) => { + state.serialize_field("inner", &self.inner)?; + state.serialize_field("type", "bytes")?; + } + DuperInner::Integer(_) => { + state.serialize_field("inner", &self.inner)?; + state.serialize_field("type", "integer")?; + } + DuperInner::Float(_) => { + state.serialize_field("inner", &self.inner)?; + state.serialize_field("type", "float")?; + } + DuperInner::Boolean(_) => { + state.serialize_field("inner", &self.inner)?; + state.serialize_field("type", "boolean")?; + } + DuperInner::Null => { + state.serialize_field("inner", &self.inner)?; + state.serialize_field("type", "null")?; + } + } + state.end() + } + + /// A function that attempts to deserialize a struct containing + /// `identifier`, `inner`, and `type` fields into the appropriate Duper + /// value. + pub fn deserialize_meta<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + 'de: 'a, + { + let duper_value_meta = DeDuperValue::deserialize(deserializer)?; + Ok(DuperValue { + identifier: duper_value_meta.identifier, + inner: duper_value_meta.inner.try_into().map_err(Error::custom)?, + }) + } +} + +struct SerDuperValue<'b>(&'b DuperValue<'b>); + +impl<'b> Serialize for SerDuperValue<'b> { + fn serialize(&self, serializer: S) -> Result + where + S: serde_core::Serializer, + { + self.0.serialize_meta(serializer) + } +} + +struct SerDuperObject<'b>(&'b DuperObject<'b>); + +impl<'b> Serialize for SerDuperObject<'b> { + fn serialize(&self, serializer: S) -> Result + where + S: serde_core::Serializer, + { + let mut map = serializer.serialize_map(Some(self.0.len()))?; + for (key, value) in self.0.iter() { + map.serialize_entry(key, &SerDuperValue(value))?; + } + map.end() + } +} + +struct SerDuperArray<'b>(&'b DuperArray<'b>); + +impl<'b> Serialize for SerDuperArray<'b> { + fn serialize(&self, serializer: S) -> Result + where + S: serde_core::Serializer, + { + let mut seq = serializer.serialize_seq(Some(self.0.len()))?; + for element in self.0.iter() { + seq.serialize_element(&SerDuperValue(element))?; + } + seq.end() + } +} + +struct SerDuperTuple<'b>(&'b DuperTuple<'b>); + +impl<'b> Serialize for SerDuperTuple<'b> { + fn serialize(&self, serializer: S) -> Result + where + S: serde_core::Serializer, + { + let mut tup = serializer.serialize_tuple(self.0.len())?; + for element in self.0.iter() { + tup.serialize_element(&SerDuperValue(element))?; + } + tup.end() + } +} + +struct DeDuperValue<'b> { + identifier: Option>, + inner: DeDuperInner<'b>, +} + +impl<'b> TryFrom> for DuperValue<'b> { + type Error = DuperObjectTryFromError<'b>; + + fn try_from(value: DeDuperValue<'b>) -> Result { + Ok(DuperValue { + identifier: value.identifier, + inner: value.inner.try_into()?, + }) + } +} + +struct DeDuperObject<'b>(Vec<(DuperKey<'b>, DeDuperValue<'b>)>); +struct DeDuperArray<'b>(Vec>); +struct DeDuperTuple<'b>(Vec>); + +enum DeDuperInner<'b> { + Object(DeDuperObject<'b>), + Array(DeDuperArray<'b>), + Tuple(DeDuperTuple<'b>), + String(DuperString<'b>), + Bytes(DuperBytes<'b>), + Integer(i64), + Float(f64), + Boolean(bool), + Null, +} + +impl Display for DeDuperInner<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + DeDuperInner::Object(_) => "object", + DeDuperInner::Array(_) => "array", + DeDuperInner::Tuple(_) => "tuple", + DeDuperInner::String(_) => "string", + DeDuperInner::Bytes(_) => "bytes", + DeDuperInner::Integer(_) => "integer", + DeDuperInner::Float(_) => "float", + DeDuperInner::Boolean(_) => "boolean", + DeDuperInner::Null => "null", + }) + } +} + +impl<'b> TryFrom> for DuperInner<'b> { + type Error = DuperObjectTryFromError<'b>; + + fn try_from(value: DeDuperInner<'b>) -> Result { + match value { + DeDuperInner::Object(object) => Ok(DuperInner::Object(DuperObject::try_from( + object + .0 + .into_iter() + .map(|(key, value)| DuperValue::try_from(value).map(|value| (key, value))) + .collect::, _>>()?, + )?)), + DeDuperInner::Array(array) => Ok(DuperInner::Array(DuperArray::from( + array + .0 + .into_iter() + .map(DuperValue::try_from) + .collect::, _>>()?, + ))), + DeDuperInner::Tuple(tuple) => Ok(DuperInner::Tuple(DuperTuple::from( + tuple + .0 + .into_iter() + .map(DuperValue::try_from) + .collect::, _>>()?, + ))), + DeDuperInner::String(string) => Ok(DuperInner::String(string)), + DeDuperInner::Bytes(bytes) => Ok(DuperInner::Bytes(bytes)), + DeDuperInner::Integer(integet) => Ok(DuperInner::Integer(integet)), + DeDuperInner::Float(float) => Ok(DuperInner::Float(float)), + DeDuperInner::Boolean(boolean) => Ok(DuperInner::Boolean(boolean)), + DeDuperInner::Null => Ok(DuperInner::Null), + } + } +} + +struct DeDuperInnerVisitor; + +impl<'de> Visitor<'de> for DeDuperInnerVisitor { + type Value = DeDuperInner<'de>; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a meta Duper inner value") + } + + fn visit_bool(self, v: bool) -> Result + where + E: Error, + { + Ok(DeDuperInner::Boolean(v)) + } + + fn visit_i64(self, v: i64) -> Result + where + E: Error, + { + Ok(DeDuperInner::Integer(v)) + } + + fn visit_i128(self, v: i128) -> Result + where + E: Error, + { + if let Ok(v) = i64::try_from(v) { + Ok(DeDuperInner::Integer(v)) + } else if let float = v as f64 + && float as i128 == v + { + Ok(DeDuperInner::Float(float)) + } else { + Ok(DeDuperInner::String(DuperString::from(v.to_string()))) + } + } + + fn visit_u8(self, v: u8) -> Result + where + E: Error, + { + self.visit_i64(v as i64) + } + + fn visit_u16(self, v: u16) -> Result + where + E: Error, + { + self.visit_i64(v as i64) + } + + fn visit_u32(self, v: u32) -> Result + where + E: Error, + { + self.visit_i64(v as i64) + } + + fn visit_u64(self, v: u64) -> Result + where + E: Error, + { + if let Ok(v) = i64::try_from(v) { + Ok(DeDuperInner::Integer(v)) + } else if let float = v as f64 + && float as u64 == v + { + Ok(DeDuperInner::Float(float)) + } else { + Ok(DeDuperInner::String(DuperString::from(v.to_string()))) + } + } + + fn visit_u128(self, v: u128) -> Result + where + E: Error, + { + if let Ok(v) = i64::try_from(v) { + Ok(DeDuperInner::Integer(v)) + } else if let float = v as f64 + && float as u128 == v + { + Ok(DeDuperInner::Float(float)) + } else { + Ok(DeDuperInner::String(DuperString::from(v.to_string()))) + } + } + + fn visit_f64(self, v: f64) -> Result + where + E: Error, + { + Ok(DeDuperInner::Float(v)) + } + + fn visit_str(self, v: &str) -> Result + where + E: Error, + { + self.visit_string(v.to_string()) + } + + fn visit_borrowed_str(self, v: &'de str) -> Result + where + E: Error, + { + Ok(DeDuperInner::String(DuperString::from(Cow::Borrowed(v)))) + } + + fn visit_string(self, v: String) -> Result + where + E: Error, + { + Ok(DeDuperInner::String(DuperString::from(v))) + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: Error, + { + self.visit_byte_buf(v.to_vec()) + } + + fn visit_borrowed_bytes(self, v: &'de [u8]) -> Result + where + E: Error, + { + Ok(DeDuperInner::Bytes(DuperBytes::from(Cow::Borrowed(v)))) + } + + fn visit_byte_buf(self, v: Vec) -> Result + where + E: Error, + { + Ok(DeDuperInner::Bytes(DuperBytes::from(v))) + } + + fn visit_none(self) -> Result + where + E: Error, + { + Ok(DeDuperInner::Null) + } + + fn visit_some(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_any(self) + } + + fn visit_unit(self) -> Result + where + E: Error, + { + Ok(DeDuperInner::Tuple(DeDuperTuple(vec![]))) + } + + fn visit_newtype_struct(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_any(self) + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut vec = seq + .size_hint() + .map(|len| Vec::with_capacity(len)) + .unwrap_or_default(); + while let Some(element) = seq.next_element()? { + vec.push(element); + } + Ok(DeDuperInner::Array(DeDuperArray(vec))) + } + + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + let mut vec = map + .size_hint() + .map(|len| Vec::with_capacity(len)) + .unwrap_or_default(); + while let Some(element) = map.next_entry()? { + vec.push(element); + } + Ok(DeDuperInner::Object(DeDuperObject(vec))) + } +} + +impl<'de> Deserialize<'de> for DeDuperInner<'de> { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_any(DeDuperInnerVisitor {}) + } +} + +struct DeDuperValueVisitor; + +enum DeDuperType { + Object, + Array, + Tuple, + String, + Bytes, + Integer, + Float, + Boolean, + Null, +} + +impl Display for DeDuperType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + DeDuperType::Object => "object", + DeDuperType::Array => "array", + DeDuperType::Tuple => "tuple", + DeDuperType::String => "string", + DeDuperType::Bytes => "bytes", + DeDuperType::Integer => "integer", + DeDuperType::Float => "float", + DeDuperType::Boolean => "boolean", + DeDuperType::Null => "null", + }) + } +} + +impl<'b> TryFrom<&'b str> for DeDuperType { + type Error = &'b str; + + fn try_from(value: &'b str) -> Result { + match value { + "object" => Ok(DeDuperType::Object), + "array" => Ok(DeDuperType::Array), + "tuple" => Ok(DeDuperType::Tuple), + "string" => Ok(DeDuperType::String), + "bytes" => Ok(DeDuperType::Bytes), + "integer" => Ok(DeDuperType::Integer), + "float" => Ok(DeDuperType::Float), + "boolean" => Ok(DeDuperType::Boolean), + "null" => Ok(DeDuperType::Null), + _ => Err(value), + } + } +} + +impl<'de> Visitor<'de> for DeDuperValueVisitor { + type Value = DeDuperValue<'de>; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a meta Duper value") + } + + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + let mut found_identifier = false; + let mut identifier: Option> = None; + let mut inner: Option> = None; + let mut typ: Option = None; + + while let Some(key) = map.next_key::()? { + match key.as_str() { + "identifier" => { + if found_identifier { + return Err(Error::duplicate_field("identifier")); + } + found_identifier = true; + identifier = map.next_value()?; + } + "inner" => { + if inner.is_some() { + return Err(Error::duplicate_field("inner")); + } + let wrapper: DeDuperInner = map.next_value()?; + inner = Some(wrapper); + } + "type" => { + if typ.is_some() { + return Err(Error::duplicate_field("type")); + } + let typ_tag: &str = map.next_value()?; + typ = Some(DeDuperType::try_from(typ_tag).map_err(|_| + Error::invalid_value( + serde_core::de::Unexpected::Str(typ_tag), + &"one of: object, array, tuple, string, bytes, integer, float, boolean, null", + ))?); + } + _ => { + return Err(Error::unknown_field( + key.as_str(), + &["identifier", "inner", "type"], + )); + } + } + } + + let inner = inner.ok_or_else(|| Error::missing_field("inner"))?; + let typ = typ.ok_or_else(|| Error::missing_field("type"))?; + + let inner = match (inner, typ) { + // Direct matches + (DeDuperInner::Object(object), DeDuperType::Object) => DeDuperInner::Object(object), + (DeDuperInner::Array(array), DeDuperType::Array) => DeDuperInner::Array(array), + (DeDuperInner::Tuple(tuple), DeDuperType::Tuple) => DeDuperInner::Tuple(tuple), + (DeDuperInner::String(string), DeDuperType::String) => DeDuperInner::String(string), + (DeDuperInner::Bytes(bytes), DeDuperType::Bytes) => DeDuperInner::Bytes(bytes), + (DeDuperInner::Integer(integer), DeDuperType::Integer) => { + DeDuperInner::Integer(integer) + } + (DeDuperInner::Float(float), DeDuperType::Float) => DeDuperInner::Float(float), + (DeDuperInner::Boolean(boolean), DeDuperType::Boolean) => { + DeDuperInner::Boolean(boolean) + } + (DeDuperInner::Null, DeDuperType::Null) => DeDuperInner::Null, + // Swapped arrays/tuples + (DeDuperInner::Array(array), DeDuperType::Tuple) => { + DeDuperInner::Tuple(DeDuperTuple(array.0)) + } + (DeDuperInner::Tuple(tuple), DeDuperType::Array) => { + DeDuperInner::Array(DeDuperArray(tuple.0)) + } + // Safe integer wrappers + (DeDuperInner::Float(float), DeDuperType::Integer) + if matches!( + identifier.as_ref().map(AsRef::as_ref), + Some("I128") | Some("U64") | Some("U128") + ) => + { + DeDuperInner::Float(float) + } + (DeDuperInner::String(string), DeDuperType::Integer) + if matches!( + identifier.as_ref().map(AsRef::as_ref), + Some("I128") | Some("U64") | Some("U128") + ) => + { + DeDuperInner::String(string) + } + // Fallback + (inner, typ) => { + return Err(Error::custom(format!( + "type '{typ}' doesn't match inner type '{inner}'" + ))); + } + }; + + Ok(DeDuperValue { identifier, inner }) + } +} + +impl<'de> Deserialize<'de> for DeDuperValue<'de> { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_struct( + "DuperValue", + &["identifier", "inner", "type"], + DeDuperValueVisitor, + ) + } +} + +#[cfg(test)] +mod serde_meta_tests { + use std::borrow::Cow; + + use insta::assert_snapshot; + + use crate::{ + DuperArray, DuperBytes, DuperIdentifier, DuperInner, DuperKey, DuperObject, DuperString, + DuperTuple, DuperValue, PrettyPrinter, + serde::{de::Deserializer, ser::Serializer}, + }; + + fn serialize_meta(value: &DuperValue<'_>) -> String { + let ser = value + .serialize_meta(&mut Serializer::new()) + .expect("should serialize"); + PrettyPrinter::new(false, " ").unwrap().pretty_print(ser) + } + + fn deserialize_meta(value: &str) -> DuperValue<'_> { + DuperValue::deserialize_meta(&mut Deserializer::from_string(value).expect("should parse")) + .expect("should deserialize") + } + + #[test] + fn serialize_object() { + let value = DuperValue { + identifier: None, + inner: DuperInner::Object(DuperObject(vec![])), + }; + let serialized = serialize_meta(&value); + assert_snapshot!(serialized); + let deserialized = deserialize_meta(&serialized); + assert_eq!(value, deserialized); + assert_eq!(deserialized.identifier, None); + + let value = DuperValue { + identifier: Some(DuperIdentifier::try_from("Outer").expect("valid identifier")), + inner: DuperInner::Object(DuperObject(vec![( + DuperKey::from("foo"), + DuperValue { + identifier: Some(DuperIdentifier::try_from("Inner").expect("valid identifier")), + inner: DuperInner::Object(DuperObject(vec![])), + }, + )])), + }; + let serialized = serialize_meta(&value); + assert_snapshot!(serialized); + let deserialized = deserialize_meta(&serialized); + assert_eq!(value, deserialized); + assert_eq!( + deserialized.identifier, + Some(DuperIdentifier(Cow::Borrowed("Outer"))) + ); + } + + #[test] + fn serialize_array() { + let value = DuperValue { + identifier: None, + inner: DuperInner::Array(DuperArray(vec![])), + }; + let serialized = serialize_meta(&value); + assert_snapshot!(serialized); + let deserialized = deserialize_meta(&serialized); + assert_eq!(value, deserialized); + assert_eq!(deserialized.identifier, None); + + let value = DuperValue { + identifier: Some(DuperIdentifier::try_from("Outer").expect("valid identifier")), + inner: DuperInner::Array(DuperArray(vec![DuperValue { + identifier: Some(DuperIdentifier::try_from("Inner").expect("valid identifier")), + inner: DuperInner::Array(DuperArray(vec![])), + }])), + }; + let serialized = serialize_meta(&value); + assert_snapshot!(serialized); + let deserialized = deserialize_meta(&serialized); + assert_eq!(value, deserialized); + assert_eq!( + deserialized.identifier, + Some(DuperIdentifier(Cow::Borrowed("Outer"))) + ); + } + + #[test] + fn serialize_tuple() { + let value = DuperValue { + identifier: None, + inner: DuperInner::Tuple(DuperTuple(vec![])), + }; + let serialized = serialize_meta(&value); + assert_snapshot!(serialized); + let deserialized = deserialize_meta(&serialized); + assert_eq!(value, deserialized); + assert_eq!(deserialized.identifier, None); + + let value = DuperValue { + identifier: Some(DuperIdentifier::try_from("Outer").expect("valid identifier")), + inner: DuperInner::Tuple(DuperTuple(vec![DuperValue { + identifier: Some(DuperIdentifier::try_from("Inner").expect("valid identifier")), + inner: DuperInner::Tuple(DuperTuple(vec![])), + }])), + }; + let serialized = serialize_meta(&value); + assert_snapshot!(serialized); + let deserialized = deserialize_meta(&serialized); + assert_eq!(value, deserialized); + assert_eq!( + deserialized.identifier, + Some(DuperIdentifier(Cow::Borrowed("Outer"))) + ); + } + + #[test] + fn serialize_scalars() { + let value = DuperValue { + identifier: None, + inner: DuperInner::Object(DuperObject(vec![ + ( + DuperKey::from("string"), + DuperValue { + identifier: None, + inner: DuperInner::String(DuperString::from("Hello world!")), + }, + ), + ( + DuperKey::from("bytes"), + DuperValue { + identifier: None, + inner: DuperInner::Bytes(DuperBytes::from(&br"/\"[..])), + }, + ), + ( + DuperKey::from("integer"), + DuperValue { + identifier: None, + inner: DuperInner::Integer(1337), + }, + ), + ( + DuperKey::from("float"), + DuperValue { + identifier: None, + inner: DuperInner::Float(8.25), + }, + ), + ( + DuperKey::from("boolean"), + DuperValue { + identifier: None, + inner: DuperInner::Boolean(true), + }, + ), + ( + DuperKey::from("null"), + DuperValue { + identifier: None, + inner: DuperInner::Null, + }, + ), + ])), + }; + let serialized = serialize_meta(&value); + assert_snapshot!(serialized); + let deserialized = deserialize_meta(&serialized); + assert_eq!(value, deserialized); + assert_eq!(deserialized.identifier, None); + + let value = DuperValue { + identifier: Some(DuperIdentifier::try_from("MyScalars").expect("valid identifier")), + inner: DuperInner::Array(DuperArray(vec![ + DuperValue { + identifier: Some( + DuperIdentifier::try_from("MyString").expect("valid identifier"), + ), + inner: DuperInner::String(DuperString::from("Hello world!")), + }, + DuperValue { + identifier: Some( + DuperIdentifier::try_from("MyBytes").expect("valid identifier"), + ), + inner: DuperInner::Bytes(DuperBytes::from(&br"/\"[..])), + }, + DuperValue { + identifier: Some(DuperIdentifier::try_from("MyInt").expect("valid identifier")), + inner: DuperInner::Integer(1337), + }, + DuperValue { + identifier: Some( + DuperIdentifier::try_from("MyFloat").expect("valid identifier"), + ), + inner: DuperInner::Float(8.25), + }, + DuperValue { + identifier: Some( + DuperIdentifier::try_from("MyBool").expect("valid identifier"), + ), + inner: DuperInner::Boolean(true), + }, + DuperValue { + identifier: Some( + DuperIdentifier::try_from("Mysterious").expect("valid identifier"), + ), + inner: DuperInner::Null, + }, + ])), + }; + let serialized = serialize_meta(&value); + assert_snapshot!(serialized); + let deserialized = deserialize_meta(&serialized); + assert_eq!(value, deserialized); + assert_eq!( + deserialized.identifier, + Some(DuperIdentifier(Cow::Borrowed("MyScalars"))) + ); + } +} diff --git a/duper/src/serde/mod.rs b/duper/src/serde/mod.rs new file mode 100644 index 0000000..e9b637d --- /dev/null +++ b/duper/src/serde/mod.rs @@ -0,0 +1,642 @@ +pub mod de; +pub mod error; +pub mod meta; +pub mod ser; + +use std::borrow::Cow; + +use serde_core::{ + Deserializer, Serialize, + de::{Deserialize, Error, MapAccess, SeqAccess, Visitor}, + ser::{SerializeMap, SerializeSeq, SerializeTuple}, +}; + +use crate::{ + DuperArray, DuperBytes, DuperIdentifier, DuperInner, DuperKey, DuperObject, DuperString, + DuperTuple, DuperValue, +}; + +impl<'a> Serialize for DuperValue<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde_core::Serializer, + { + self.inner.serialize(serializer) + } +} + +impl<'a> Serialize for DuperIdentifier<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde_core::Serializer, + { + serializer.serialize_str(self.as_ref()) + } +} + +impl<'a> Serialize for DuperInner<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde_core::Serializer, + { + match &self { + DuperInner::Object(object) => { + let mut map = serializer.serialize_map(Some(object.len()))?; + for (key, value) in object.iter() { + map.serialize_entry(key.as_ref(), value)?; + } + map.end() + } + DuperInner::Array(array) => { + let mut seq = serializer.serialize_seq(Some(array.len()))?; + for element in array.iter() { + seq.serialize_element(element)?; + } + seq.end() + } + DuperInner::Tuple(tuple) => { + let mut tup = serializer.serialize_tuple(tuple.len())?; + for element in tuple.iter() { + tup.serialize_element(element)?; + } + tup.end() + } + DuperInner::String(string) => serializer.serialize_str(string.as_ref()), + DuperInner::Bytes(bytes) => serializer.serialize_bytes(bytes.as_ref()), + DuperInner::Integer(integer) => serializer.serialize_i64(*integer), + DuperInner::Float(float) => serializer.serialize_f64(*float), + DuperInner::Boolean(boolean) => serializer.serialize_bool(*boolean), + DuperInner::Null => serializer.serialize_none(), + } + } +} + +impl<'a> Serialize for DuperKey<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde_core::Serializer, + { + serializer.serialize_str(self.as_ref()) + } +} + +impl<'de> Deserialize<'de> for DuperValue<'de> { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct DuperVisitor; + + impl<'de> Visitor<'de> for DuperVisitor { + type Value = DuperValue<'de>; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a Duper value") + } + + fn visit_bool(self, v: bool) -> Result + where + E: Error, + { + Ok(DuperValue { + identifier: None, + inner: DuperInner::Boolean(v), + }) + } + + fn visit_i64(self, v: i64) -> Result + where + E: Error, + { + Ok(DuperValue { + identifier: None, + inner: DuperInner::Integer(v), + }) + } + + fn visit_i128(self, v: i128) -> Result + where + E: Error, + { + if let Ok(v) = i64::try_from(v) { + Ok(DuperValue { + identifier: None, + inner: DuperInner::Integer(v), + }) + } else if let float = v as f64 + && float as i128 == v + { + Ok(DuperValue { + identifier: None, + inner: DuperInner::Float(float), + }) + } else { + Ok(DuperValue { + identifier: Some( + DuperIdentifier::try_from(Cow::Borrowed("I128")) + .expect("valid identifier"), + ), + inner: DuperInner::String(DuperString::from(v.to_string())), + }) + } + } + + fn visit_u8(self, v: u8) -> Result + where + E: Error, + { + self.visit_i64(v as i64) + } + + fn visit_u16(self, v: u16) -> Result + where + E: Error, + { + self.visit_i64(v as i64) + } + + fn visit_u32(self, v: u32) -> Result + where + E: Error, + { + self.visit_i64(v as i64) + } + + fn visit_u64(self, v: u64) -> Result + where + E: Error, + { + if let Ok(v) = i64::try_from(v) { + Ok(DuperValue { + identifier: None, + inner: DuperInner::Integer(v), + }) + } else if let float = v as f64 + && float as u64 == v + { + Ok(DuperValue { + identifier: None, + inner: DuperInner::Float(float), + }) + } else { + Ok(DuperValue { + identifier: Some( + DuperIdentifier::try_from(Cow::Borrowed("U64")) + .expect("valid identifier"), + ), + inner: DuperInner::String(DuperString::from(v.to_string())), + }) + } + } + + fn visit_u128(self, v: u128) -> Result + where + E: Error, + { + if let Ok(v) = i64::try_from(v) { + Ok(DuperValue { + identifier: None, + inner: DuperInner::Integer(v), + }) + } else if let float = v as f64 + && float as u128 == v + { + Ok(DuperValue { + identifier: None, + inner: DuperInner::Float(float), + }) + } else { + Ok(DuperValue { + identifier: Some( + DuperIdentifier::try_from(Cow::Borrowed("U128")) + .expect("valid identifier"), + ), + inner: DuperInner::String(DuperString::from(v.to_string())), + }) + } + } + + fn visit_f64(self, v: f64) -> Result + where + E: Error, + { + Ok(DuperValue { + identifier: None, + inner: DuperInner::Float(v), + }) + } + + fn visit_str(self, v: &str) -> Result + where + E: Error, + { + self.visit_string(v.to_string()) + } + + fn visit_borrowed_str(self, v: &'de str) -> Result + where + E: Error, + { + Ok(DuperValue { + identifier: None, + inner: DuperInner::String(DuperString::from(Cow::Borrowed(v))), + }) + } + + fn visit_string(self, v: String) -> Result + where + E: Error, + { + Ok(DuperValue { + identifier: None, + inner: DuperInner::String(DuperString::from(v)), + }) + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: Error, + { + self.visit_byte_buf(v.to_vec()) + } + + fn visit_borrowed_bytes(self, v: &'de [u8]) -> Result + where + E: Error, + { + Ok(DuperValue { + identifier: None, + inner: DuperInner::Bytes(DuperBytes::from(Cow::Borrowed(v))), + }) + } + + fn visit_byte_buf(self, v: Vec) -> Result + where + E: Error, + { + Ok(DuperValue { + identifier: None, + inner: DuperInner::Bytes(DuperBytes::from(v)), + }) + } + + fn visit_none(self) -> Result + where + E: Error, + { + Ok(DuperValue { + identifier: None, + inner: DuperInner::Null, + }) + } + + fn visit_some(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_any(self) + } + + fn visit_unit(self) -> Result + where + E: Error, + { + Ok(DuperValue { + identifier: None, + inner: DuperInner::Tuple(DuperTuple::from(vec![])), + }) + } + + fn visit_newtype_struct(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_any(self) + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut vec = seq + .size_hint() + .map(|len| Vec::with_capacity(len)) + .unwrap_or_default(); + while let Some(element) = seq.next_element()? { + vec.push(element); + } + Ok(DuperValue { + identifier: None, + inner: DuperInner::Array(DuperArray::from(vec)), + }) + } + + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + let mut vec = map + .size_hint() + .map(|len| Vec::with_capacity(len)) + .unwrap_or_default(); + while let Some(element) = map.next_entry()? { + vec.push(element); + } + Ok(DuperValue { + identifier: None, + inner: DuperInner::Object(DuperObject::try_from(vec).map_err(Error::custom)?), + }) + } + } + + deserializer.deserialize_any(DuperVisitor {}) + } +} + +impl<'de> Deserialize<'de> for DuperKey<'de> { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct DuperKeyVisitor; + + impl<'de> Visitor<'de> for DuperKeyVisitor { + type Value = DuperKey<'de>; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a Duper key") + } + + fn visit_str(self, v: &str) -> Result + where + E: Error, + { + self.visit_string(v.to_string()) + } + + fn visit_borrowed_str(self, v: &'de str) -> Result + where + E: Error, + { + Ok(DuperKey::from(Cow::Borrowed(v))) + } + + fn visit_string(self, v: String) -> Result + where + E: Error, + { + Ok(DuperKey::from(v)) + } + } + + deserializer.deserialize_any(DuperKeyVisitor) + } +} + +impl<'de> Deserialize<'de> for DuperIdentifier<'de> { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct DuperIdentifierVisitor; + + impl<'de> Visitor<'de> for DuperIdentifierVisitor { + type Value = DuperIdentifier<'de>; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a Duper identifier") + } + + fn visit_str(self, v: &str) -> Result + where + E: Error, + { + self.visit_string(v.to_string()) + } + + fn visit_borrowed_str(self, v: &'de str) -> Result + where + E: Error, + { + DuperIdentifier::try_from_lossy(Cow::Borrowed(v)).map_err(Error::custom) + } + + fn visit_string(self, v: String) -> Result + where + E: Error, + { + DuperIdentifier::try_from_lossy(Cow::Owned(v)).map_err(Error::custom) + } + } + + deserializer.deserialize_any(DuperIdentifierVisitor) + } +} + +#[cfg(test)] +mod serde_tests { + use insta::assert_snapshot; + use serde::{Deserialize, Serialize}; + + use crate::{ + DuperArray, DuperBytes, DuperIdentifier, DuperInner, DuperKey, DuperObject, DuperString, + DuperTuple, DuperValue, PrettyPrinter, + serde::{de::Deserializer, ser::Serializer}, + }; + + fn serialize_duper(value: &DuperValue<'_>) -> String { + let ser = value + .serialize(&mut Serializer::new()) + .expect("should serialize"); + PrettyPrinter::new(false, " ").unwrap().pretty_print(ser) + } + + fn deserialize_duper(value: &str) -> DuperValue<'_> { + DuperValue::deserialize(&mut Deserializer::from_string(value).expect("should parse")) + .expect("should deserialize") + } + + #[test] + fn serialize_object() { + let value = DuperValue { + identifier: None, + inner: DuperInner::Object(DuperObject(vec![])), + }; + let serialized = serialize_duper(&value); + assert_snapshot!(serialized); + let deserialized = deserialize_duper(&serialized); + assert_eq!(value, deserialized); + + let value = DuperValue { + identifier: Some(DuperIdentifier::try_from("Outer").expect("valid identifier")), + inner: DuperInner::Object(DuperObject(vec![( + DuperKey::from("foo"), + DuperValue { + identifier: Some(DuperIdentifier::try_from("Inner").expect("valid identifier")), + inner: DuperInner::Object(DuperObject(vec![])), + }, + )])), + }; + let serialized = serialize_duper(&value); + assert_snapshot!(serialized); + let deserialized = deserialize_duper(&serialized); + assert_eq!(value, deserialized); + } + + #[test] + fn serialize_array() { + let value = DuperValue { + identifier: None, + inner: DuperInner::Array(DuperArray(vec![])), + }; + let serialized = serialize_duper(&value); + assert_snapshot!(serialized); + let deserialized = deserialize_duper(&serialized); + assert_eq!(value, deserialized); + + let value = DuperValue { + identifier: Some(DuperIdentifier::try_from("Outer").expect("valid identifier")), + inner: DuperInner::Array(DuperArray(vec![DuperValue { + identifier: Some(DuperIdentifier::try_from("Inner").expect("valid identifier")), + inner: DuperInner::Array(DuperArray(vec![])), + }])), + }; + let serialized = serialize_duper(&value); + assert_snapshot!(serialized); + let deserialized = deserialize_duper(&serialized); + assert_eq!(value, deserialized); + } + + #[test] + fn serialize_tuple() { + let value = DuperValue { + identifier: None, + inner: DuperInner::Tuple(DuperTuple(vec![])), + }; + let serialized = serialize_duper(&value); + assert_snapshot!(serialized); + let deserialized = deserialize_duper(&serialized); + assert_eq!(value, deserialized); + + let value = DuperValue { + identifier: Some(DuperIdentifier::try_from("Outer").expect("valid identifier")), + inner: DuperInner::Tuple(DuperTuple(vec![DuperValue { + identifier: Some(DuperIdentifier::try_from("Inner").expect("valid identifier")), + inner: DuperInner::Tuple(DuperTuple(vec![])), + }])), + }; + let serialized = serialize_duper(&value); + assert_snapshot!(serialized); + let deserialized = deserialize_duper(&serialized); + assert_eq!( + DuperValue { + identifier: None, + // Unfortunately, Serde deserializes non-unit tuples into arrays + inner: DuperInner::Array(DuperArray(vec![DuperValue { + identifier: None, + inner: DuperInner::Tuple(DuperTuple(vec![])), + }])), + }, + deserialized, + ); + } + + #[test] + fn serialize_scalars() { + let value = DuperValue { + identifier: None, + inner: DuperInner::Object(DuperObject(vec![ + ( + DuperKey::from("string"), + DuperValue { + identifier: None, + inner: DuperInner::String(DuperString::from("Hello world!")), + }, + ), + ( + DuperKey::from("bytes"), + DuperValue { + identifier: None, + inner: DuperInner::Bytes(DuperBytes::from(&br"/\"[..])), + }, + ), + ( + DuperKey::from("integer"), + DuperValue { + identifier: None, + inner: DuperInner::Integer(1337), + }, + ), + ( + DuperKey::from("float"), + DuperValue { + identifier: None, + inner: DuperInner::Float(8.25), + }, + ), + ( + DuperKey::from("boolean"), + DuperValue { + identifier: None, + inner: DuperInner::Boolean(true), + }, + ), + ( + DuperKey::from("null"), + DuperValue { + identifier: None, + inner: DuperInner::Null, + }, + ), + ])), + }; + let serialized = serialize_duper(&value); + assert_snapshot!(serialized); + let deserialized = deserialize_duper(&serialized); + assert_eq!(value, deserialized); + + let value = DuperValue { + identifier: Some(DuperIdentifier::try_from("MyScalars").expect("valid identifier")), + inner: DuperInner::Array(DuperArray(vec![ + DuperValue { + identifier: Some( + DuperIdentifier::try_from("MyString").expect("valid identifier"), + ), + inner: DuperInner::String(DuperString::from("Hello world!")), + }, + DuperValue { + identifier: Some( + DuperIdentifier::try_from("MyBytes").expect("valid identifier"), + ), + inner: DuperInner::Bytes(DuperBytes::from(&br"/\"[..])), + }, + DuperValue { + identifier: Some(DuperIdentifier::try_from("MyInt").expect("valid identifier")), + inner: DuperInner::Integer(1337), + }, + DuperValue { + identifier: Some( + DuperIdentifier::try_from("MyFloat").expect("valid identifier"), + ), + inner: DuperInner::Float(8.25), + }, + DuperValue { + identifier: Some( + DuperIdentifier::try_from("MyBool").expect("valid identifier"), + ), + inner: DuperInner::Boolean(true), + }, + DuperValue { + identifier: Some( + DuperIdentifier::try_from("Mysterious").expect("valid identifier"), + ), + inner: DuperInner::Null, + }, + ])), + }; + let serialized = serialize_duper(&value); + assert_snapshot!(serialized); + let deserialized = deserialize_duper(&serialized); + assert_eq!(value, deserialized); + } +} diff --git a/serde_duper/src/ser.rs b/duper/src/serde/ser.rs similarity index 99% rename from serde_duper/src/ser.rs rename to duper/src/serde/ser.rs index be61d40..67a68f0 100644 --- a/serde_duper/src/ser.rs +++ b/duper/src/serde/ser.rs @@ -1,12 +1,12 @@ use std::{borrow::Cow, marker::PhantomData}; -use duper::{ +use crate::{ DuperArray, DuperBytes, DuperIdentifier, DuperInner, DuperKey, DuperObject, DuperString, DuperTuple, DuperValue, PrettyPrinter as DuperPrettyPrinter, Serializer as DuperSerializer, }; use serde_core::{Serialize, ser}; -use crate::Error; +use super::error::Error; /// A structure for serializing Rust values into Duper values. #[derive(Clone, Default)] diff --git a/duper/src/serde/snapshots/duper__serde__meta__serde_meta_tests__serialize_array-2.snap b/duper/src/serde/snapshots/duper__serde__meta__serde_meta_tests__serialize_array-2.snap new file mode 100644 index 0000000..554d8f1 --- /dev/null +++ b/duper/src/serde/snapshots/duper__serde__meta__serde_meta_tests__serialize_array-2.snap @@ -0,0 +1,15 @@ +--- +source: duper/src/serde/meta.rs +expression: "serialize_meta(&DuperValue\n{\n identifier:\n Some(DuperIdentifier::try_from(\"Outer\").expect(\"valid identifier\")),\n inner:\n DuperInner::Array(DuperArray(vec![DuperValue\n {\n identifier:\n Some(DuperIdentifier::try_from(\"Inner\").expect(\"valid identifier\")),\n inner: DuperInner::Array(DuperArray(vec![])),\n },])),\n})" +--- +DuperValue({ + identifier: "Outer", + inner: [ + DuperValue({ + identifier: "Inner", + inner: [], + type: "array", + }), + ], + type: "array", +}) diff --git a/duper/src/serde/snapshots/duper__serde__meta__serde_meta_tests__serialize_array.snap b/duper/src/serde/snapshots/duper__serde__meta__serde_meta_tests__serialize_array.snap new file mode 100644 index 0000000..03a93d9 --- /dev/null +++ b/duper/src/serde/snapshots/duper__serde__meta__serde_meta_tests__serialize_array.snap @@ -0,0 +1,9 @@ +--- +source: duper/src/serde/meta.rs +expression: "serialize_meta(&DuperValue\n{ identifier: None, inner: DuperInner::Array(DuperArray(vec![])), })" +--- +DuperValue({ + identifier: null, + inner: [], + type: "array", +}) diff --git a/duper/src/serde/snapshots/duper__serde__meta__serde_meta_tests__serialize_object-2.snap b/duper/src/serde/snapshots/duper__serde__meta__serde_meta_tests__serialize_object-2.snap new file mode 100644 index 0000000..5ff1178 --- /dev/null +++ b/duper/src/serde/snapshots/duper__serde__meta__serde_meta_tests__serialize_object-2.snap @@ -0,0 +1,15 @@ +--- +source: duper/src/serde/meta.rs +expression: serialized +--- +DuperValue({ + identifier: "Outer", + inner: { + foo: DuperValue({ + identifier: "Inner", + inner: {}, + type: "object", + }), + }, + type: "object", +}) diff --git a/duper/src/serde/snapshots/duper__serde__meta__serde_meta_tests__serialize_object.snap b/duper/src/serde/snapshots/duper__serde__meta__serde_meta_tests__serialize_object.snap new file mode 100644 index 0000000..a2d4e43 --- /dev/null +++ b/duper/src/serde/snapshots/duper__serde__meta__serde_meta_tests__serialize_object.snap @@ -0,0 +1,9 @@ +--- +source: duper/src/serde/meta.rs +expression: serialized +--- +DuperValue({ + identifier: null, + inner: {}, + type: "object", +}) diff --git a/duper/src/serde/snapshots/duper__serde__meta__serde_meta_tests__serialize_scalars-2.snap b/duper/src/serde/snapshots/duper__serde__meta__serde_meta_tests__serialize_scalars-2.snap new file mode 100644 index 0000000..5f9efbe --- /dev/null +++ b/duper/src/serde/snapshots/duper__serde__meta__serde_meta_tests__serialize_scalars-2.snap @@ -0,0 +1,40 @@ +--- +source: duper/src/serde/meta.rs +expression: serialized +--- +DuperValue({ + identifier: "MyScalars", + inner: [ + DuperValue({ + identifier: "MyString", + inner: "Hello world!", + type: "string", + }), + DuperValue({ + identifier: "MyBytes", + inner: br"/\", + type: "bytes", + }), + DuperValue({ + identifier: "MyInt", + inner: 1337, + type: "integer", + }), + DuperValue({ + identifier: "MyFloat", + inner: 8.25, + type: "float", + }), + DuperValue({ + identifier: "MyBool", + inner: true, + type: "boolean", + }), + DuperValue({ + identifier: "Mysterious", + inner: null, + type: "null", + }), + ], + type: "array", +}) diff --git a/duper/src/serde/snapshots/duper__serde__meta__serde_meta_tests__serialize_scalars.snap b/duper/src/serde/snapshots/duper__serde__meta__serde_meta_tests__serialize_scalars.snap new file mode 100644 index 0000000..4a0bcf0 --- /dev/null +++ b/duper/src/serde/snapshots/duper__serde__meta__serde_meta_tests__serialize_scalars.snap @@ -0,0 +1,40 @@ +--- +source: duper/src/serde/meta.rs +expression: serialized +--- +DuperValue({ + identifier: null, + inner: { + string: DuperValue({ + identifier: null, + inner: "Hello world!", + type: "string", + }), + bytes: DuperValue({ + identifier: null, + inner: br"/\", + type: "bytes", + }), + integer: DuperValue({ + identifier: null, + inner: 1337, + type: "integer", + }), + float: DuperValue({ + identifier: null, + inner: 8.25, + type: "float", + }), + boolean: DuperValue({ + identifier: null, + inner: true, + type: "boolean", + }), + null: DuperValue({ + identifier: null, + inner: null, + type: "null", + }), + }, + type: "object", +}) diff --git a/duper/src/serde/snapshots/duper__serde__meta__serde_meta_tests__serialize_tuple-2.snap b/duper/src/serde/snapshots/duper__serde__meta__serde_meta_tests__serialize_tuple-2.snap new file mode 100644 index 0000000..ff22af8 --- /dev/null +++ b/duper/src/serde/snapshots/duper__serde__meta__serde_meta_tests__serialize_tuple-2.snap @@ -0,0 +1,13 @@ +--- +source: duper/src/serde/meta.rs +expression: serialized +--- +DuperValue({ + identifier: "Outer", + inner: (DuperValue({ + identifier: "Inner", + inner: (), + type: "tuple", + })), + type: "tuple", +}) diff --git a/duper/src/serde/snapshots/duper__serde__meta__serde_meta_tests__serialize_tuple.snap b/duper/src/serde/snapshots/duper__serde__meta__serde_meta_tests__serialize_tuple.snap new file mode 100644 index 0000000..b96cd8d --- /dev/null +++ b/duper/src/serde/snapshots/duper__serde__meta__serde_meta_tests__serialize_tuple.snap @@ -0,0 +1,9 @@ +--- +source: duper/src/serde/meta.rs +expression: serialized +--- +DuperValue({ + identifier: null, + inner: (), + type: "tuple", +}) diff --git a/duper/src/serde/snapshots/duper__serde__serde_tests__serialize_array-2.snap b/duper/src/serde/snapshots/duper__serde__serde_tests__serialize_array-2.snap new file mode 100644 index 0000000..acc1cf5 --- /dev/null +++ b/duper/src/serde/snapshots/duper__serde__serde_tests__serialize_array-2.snap @@ -0,0 +1,7 @@ +--- +source: duper/src/serde/mod.rs +expression: serialized +--- +[ + [], +] diff --git a/duper/src/serde/snapshots/duper__serde__serde_tests__serialize_array.snap b/duper/src/serde/snapshots/duper__serde__serde_tests__serialize_array.snap new file mode 100644 index 0000000..67400c3 --- /dev/null +++ b/duper/src/serde/snapshots/duper__serde__serde_tests__serialize_array.snap @@ -0,0 +1,5 @@ +--- +source: duper/src/serde/mod.rs +expression: serialized +--- +[] diff --git a/duper/src/serde/snapshots/duper__serde__serde_tests__serialize_object-2.snap b/duper/src/serde/snapshots/duper__serde__serde_tests__serialize_object-2.snap new file mode 100644 index 0000000..981fe7c --- /dev/null +++ b/duper/src/serde/snapshots/duper__serde__serde_tests__serialize_object-2.snap @@ -0,0 +1,7 @@ +--- +source: duper/src/serde/mod.rs +expression: serialized +--- +{ + foo: {}, +} diff --git a/duper/src/serde/snapshots/duper__serde__serde_tests__serialize_object.snap b/duper/src/serde/snapshots/duper__serde__serde_tests__serialize_object.snap new file mode 100644 index 0000000..e413a83 --- /dev/null +++ b/duper/src/serde/snapshots/duper__serde__serde_tests__serialize_object.snap @@ -0,0 +1,5 @@ +--- +source: duper/src/serde/mod.rs +expression: serialized +--- +{} diff --git a/duper/src/serde/snapshots/duper__serde__serde_tests__serialize_scalars-2.snap b/duper/src/serde/snapshots/duper__serde__serde_tests__serialize_scalars-2.snap new file mode 100644 index 0000000..65fed4a --- /dev/null +++ b/duper/src/serde/snapshots/duper__serde__serde_tests__serialize_scalars-2.snap @@ -0,0 +1,12 @@ +--- +source: duper/src/serde/mod.rs +expression: serialized +--- +[ + "Hello world!", + br"/\", + 1337, + 8.25, + true, + null, +] diff --git a/duper/src/serde/snapshots/duper__serde__serde_tests__serialize_scalars.snap b/duper/src/serde/snapshots/duper__serde__serde_tests__serialize_scalars.snap new file mode 100644 index 0000000..1e4b101 --- /dev/null +++ b/duper/src/serde/snapshots/duper__serde__serde_tests__serialize_scalars.snap @@ -0,0 +1,12 @@ +--- +source: duper/src/serde/mod.rs +expression: serialized +--- +{ + string: "Hello world!", + bytes: br"/\", + integer: 1337, + float: 8.25, + boolean: true, + null: null, +} diff --git a/duper/src/serde/snapshots/duper__serde__serde_tests__serialize_tuple-2.snap b/duper/src/serde/snapshots/duper__serde__serde_tests__serialize_tuple-2.snap new file mode 100644 index 0000000..23f9ce6 --- /dev/null +++ b/duper/src/serde/snapshots/duper__serde__serde_tests__serialize_tuple-2.snap @@ -0,0 +1,5 @@ +--- +source: duper/src/serde/mod.rs +expression: serialized +--- +(()) diff --git a/duper/src/serde/snapshots/duper__serde__serde_tests__serialize_tuple.snap b/duper/src/serde/snapshots/duper__serde__serde_tests__serialize_tuple.snap new file mode 100644 index 0000000..2f81005 --- /dev/null +++ b/duper/src/serde/snapshots/duper__serde__serde_tests__serialize_tuple.snap @@ -0,0 +1,5 @@ +--- +source: duper/src/serde/mod.rs +expression: serialized +--- +() diff --git a/duper/src/visitor/pretty_printer.rs b/duper/src/visitor/pretty_printer.rs index d27a715..8ac0a14 100644 --- a/duper/src/visitor/pretty_printer.rs +++ b/duper/src/visitor/pretty_printer.rs @@ -97,7 +97,7 @@ impl<'pp> DuperVisitor for PrettyPrinter<'pp> { } self.decrease_indentation(); self.push_indentation(); - self.buf.push_str("})\n"); + self.buf.push_str("})"); } } else if object.is_empty() { self.buf.push_str("{}"); @@ -138,7 +138,7 @@ impl<'pp> DuperVisitor for PrettyPrinter<'pp> { } self.decrease_indentation(); self.push_indentation(); - self.buf.push_str("])\n"); + self.buf.push_str("])"); } } else if array.is_empty() { self.buf.push_str("[]"); diff --git a/serde_duper/Cargo.toml b/serde_duper/Cargo.toml index b61ad3a..66b389d 100644 --- a/serde_duper/Cargo.toml +++ b/serde_duper/Cargo.toml @@ -28,8 +28,7 @@ uuid = ["dep:uuid"] [dependencies] # Core dependencies -duper = { path = "../duper", version = "0.2.1" } -miette = "7.6.0" +duper = { path = "../duper", version = "0.2.1", features = ["serde"] } pest = "2.8.3" serde_bytes = "0.11" serde_core = "1" diff --git a/serde_duper/src/lib.rs b/serde_duper/src/lib.rs index fa10574..61f4dae 100644 --- a/serde_duper/src/lib.rs +++ b/serde_duper/src/lib.rs @@ -290,18 +290,125 @@ //! pub mod bytes; -mod de; -mod error; -mod ser; pub mod types; -pub use de::{Deserializer, from_string, from_value}; +pub use duper::serde::de::Deserializer; +pub use duper::serde::error::{Error, ErrorImpl, ErrorKind, Result}; +pub use duper::serde::ser::{ + Serializer, to_duper, to_string, to_string_minified, to_string_pretty, +}; pub use duper::{ DuperArray, DuperBytes, DuperIdentifier, DuperInner, DuperKey, DuperObject, DuperString, DuperTuple, DuperValue, }; -pub use error::{Error, ErrorImpl, ErrorKind, Result}; -pub use ser::{Serializer, to_duper, to_string, to_string_minified, to_string_pretty}; #[cfg(feature = "macros")] pub use serde_duper_macros::duper; + +/// Interpret a [`DuperValue`] as an instance of type `T`. +/// +/// # Example +/// +/// ``` +/// use std::borrow::Cow; +/// use serde::Deserialize; +/// use serde_duper::{ +/// DuperBytes, DuperIdentifier, DuperInner, DuperKey, DuperObject, +/// DuperString, DuperValue, +/// }; +/// +/// #[derive(Deserialize, Debug)] +/// struct User { +/// fingerprint: Vec, +/// location: String, +/// } +/// +/// // The type of `d` is `serde_duper::DuperValue` +/// let d = DuperValue { +/// identifier: Some(DuperIdentifier::try_from(Cow::Borrowed("User")).unwrap()), +/// inner: DuperInner::Object(DuperObject::try_from(vec![ +/// ( +/// DuperKey::from(Cow::Borrowed("fingerprint")), +/// DuperValue { +/// identifier: None, +/// inner: DuperInner::Bytes(DuperBytes::from(Cow::Borrowed( +/// &b"\xF9\xBA\x14\x3B\x95\xFF\x6D\x82"[..], +/// ))), +/// } +/// ), +/// ( +/// DuperKey::from(Cow::Borrowed("location")), +/// DuperValue { +/// identifier: Some( +/// DuperIdentifier::try_from(Cow::Borrowed("City")).unwrap(), +/// ), +/// inner: DuperInner::String(DuperString::from( +/// Cow::Borrowed("Menlo Park, CA"), +/// )), +/// } +/// ), +/// ]).unwrap()), +/// }; +/// +/// let u: User = serde_duper::from_value(d).unwrap(); +/// println!("{:#?}", u); +/// ``` +/// +/// # Errors +/// +/// This conversion can fail if the structure of the input does not match the +/// structure expected by `T`, for example if `T` is a struct type but the input +/// contains something other than a Duper object. It can also fail if the +/// structure is correct but `T`'s implementation of [`Deserialize`] decides that +/// something is wrong with the data, for example required struct fields are +/// missing from the Duper object or some number is too big to fit in the +/// expected primitive type. +#[inline] +pub fn from_value<'a, T>(value: DuperValue<'a>) -> Result +where + T: serde_core::Deserialize<'a>, +{ + duper::serde::de::from_value(value) +} + +/// Deserialize an instance of type `T` from a str slice of Duper text. +/// +/// # Example +/// +/// ``` +/// use serde::Deserialize; +/// +/// #[derive(Deserialize, Debug)] +/// struct User { +/// fingerprint: Vec, +/// location: String, +/// } +/// +/// +/// // The type of `j` is `&str` +/// let j = r#" +/// User({ +/// fingerprint: b"\xF9\xBA\x14\x3B\x95\xFF\x6D\x82", +/// location: City("Menlo Park, CA"), +/// })"#; +/// +/// let u: User = serde_duper::from_string(j).unwrap(); +/// println!("{:#?}", u); +/// ``` +/// +/// # Errors +/// +/// This conversion can fail if the structure of the input does not match the +/// structure expected by `T`, for example if `T` is a struct type but the input +/// contains something other than a Duper object. It can also fail if the +/// structure is correct but `T`'s implementation of [`Deserialize`] decides that +/// something is wrong with the data, for example required struct fields are +/// missing from the Duper object or some number is too big to fit in the +/// expected primitive type. +#[inline] +pub fn from_string<'a, T>(input: &'a str) -> Result +where + T: serde_core::Deserialize<'a>, +{ + duper::serde::de::from_string(input) +}