From b3db007267b7977d0de2f3ade41758fb4435efca Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 12 Jan 2023 16:02:54 -0600 Subject: [PATCH] feat(edit): Spanned serialization support Fixes #302 --- Cargo.lock | 1 + crates/serde_spanned/src/spanned.rs | 3 - crates/toml_edit/Cargo.toml | 3 +- crates/toml_edit/src/de/array.rs | 73 +++++- crates/toml_edit/src/de/inline_table.rs | 25 +- crates/toml_edit/src/de/item.rs | 34 ++- crates/toml_edit/src/de/key.rs | 140 +++++++++++ crates/toml_edit/src/de/mod.rs | 38 ++- crates/toml_edit/src/de/spanned.rs | 74 ++++++ crates/toml_edit/src/de/table.rs | 25 +- crates/toml_edit/src/de/table_enum.rs | 4 +- crates/toml_edit/src/de/value.rs | 6 + crates/toml_edit/src/easy/mod.rs | 1 + crates/toml_edit/tests/testsuite/main.rs | 2 + crates/toml_edit/tests/testsuite/spanned.rs | 236 ++++++++++++++++++ .../tests/testsuite/spanned_impls.rs | 43 ++++ 16 files changed, 676 insertions(+), 32 deletions(-) create mode 100644 crates/toml_edit/src/de/key.rs create mode 100644 crates/toml_edit/src/de/spanned.rs create mode 100644 crates/toml_edit/tests/testsuite/spanned.rs create mode 100644 crates/toml_edit/tests/testsuite/spanned_impls.rs diff --git a/Cargo.lock b/Cargo.lock index 5add1f0c..7b4a40e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -958,6 +958,7 @@ dependencies = [ "nom8", "serde", "serde_json", + "serde_spanned", "snapbox", "toml 0.5.10", "toml-test-data", diff --git a/crates/serde_spanned/src/spanned.rs b/crates/serde_spanned/src/spanned.rs index db705959..f2cfbeae 100644 --- a/crates/serde_spanned/src/spanned.rs +++ b/crates/serde_spanned/src/spanned.rs @@ -123,19 +123,16 @@ where if visitor.next_key()? != Some(START_FIELD) { return Err(serde::de::Error::custom("spanned start key not found")); } - let start: usize = visitor.next_value()?; if visitor.next_key()? != Some(END_FIELD) { return Err(serde::de::Error::custom("spanned end key not found")); } - let end: usize = visitor.next_value()?; if visitor.next_key()? != Some(VALUE_FIELD) { return Err(serde::de::Error::custom("spanned value key not found")); } - let value: T = visitor.next_value()?; Ok(Spanned { diff --git a/crates/toml_edit/Cargo.toml b/crates/toml_edit/Cargo.toml index e15d5211..515ee0e9 100644 --- a/crates/toml_edit/Cargo.toml +++ b/crates/toml_edit/Cargo.toml @@ -38,7 +38,7 @@ pre-release-replacements = [ default = [] easy = ["serde"] perf = ["dep:kstring"] -serde = ["dep:serde", "toml_datetime/serde"] +serde = ["dep:serde", "toml_datetime/serde", "serde_spanned"] # Provide a method disable_recursion_limit to parse arbitrarily deep structures # without any consideration for overflowing the stack. Additionally you will # need to be careful around other recursive operations on the parsed result @@ -53,6 +53,7 @@ itertools = "0.10.5" serde = { version = "1.0.145", features = ["derive"], optional = true } kstring = { version = "2.0.0", features = ["max_inline"], optional = true } toml_datetime = { version = "0.5.0", path = "../toml_datetime" } +serde_spanned = { version = "0.5.0", path = "../serde_spanned", features = ["serde"], optional = true } [dev-dependencies] serde_json = "1.0.91" diff --git a/crates/toml_edit/src/de/array.rs b/crates/toml_edit/src/de/array.rs index 4aaacdad..bc398148 100644 --- a/crates/toml_edit/src/de/array.rs +++ b/crates/toml_edit/src/de/array.rs @@ -2,11 +2,12 @@ use crate::de::Error; pub(crate) struct ArrayDeserializer { input: Vec, + span: Option>, } impl ArrayDeserializer { - pub(crate) fn new(input: Vec) -> Self { - Self { input } + pub(crate) fn new(input: Vec, span: Option>) -> Self { + Self { input, span } } } @@ -20,10 +21,36 @@ impl<'de> serde::Deserializer<'de> for ArrayDeserializer { visitor.visit_seq(ArraySeqAccess::new(self.input)) } + fn deserialize_struct( + self, + name: &'static str, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + if super::is_spanned(name, fields) { + if let Some(span) = self.span.clone() { + return visitor.visit_map(super::SpannedDeserializer::new(self, span)); + } + } + + self.deserialize_any(visitor) + } + serde::forward_to_deserialize_any! { bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq bytes byte_buf map option unit newtype_struct - ignored_any unit_struct tuple_struct tuple enum identifier struct + ignored_any unit_struct tuple_struct tuple enum identifier + } +} + +impl<'de> serde::de::IntoDeserializer<'de, crate::de::Error> for ArrayDeserializer { + type Deserializer = Self; + + fn into_deserializer(self) -> Self { + self } } @@ -37,10 +64,28 @@ impl<'de> serde::Deserializer<'de> for crate::Array { visitor.visit_seq(ArraySeqAccess::with_array(self)) } + fn deserialize_struct( + self, + name: &'static str, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + if super::is_spanned(name, fields) { + if let Some(span) = self.span() { + return visitor.visit_map(super::SpannedDeserializer::new(self, span)); + } + } + + self.deserialize_any(visitor) + } + serde::forward_to_deserialize_any! { bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq bytes byte_buf map option unit newtype_struct - ignored_any unit_struct tuple_struct tuple enum identifier struct + ignored_any unit_struct tuple_struct tuple enum identifier } } @@ -62,10 +107,28 @@ impl<'de> serde::Deserializer<'de> for crate::ArrayOfTables { visitor.visit_seq(ArraySeqAccess::with_array_of_tables(self)) } + fn deserialize_struct( + self, + name: &'static str, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + if super::is_spanned(name, fields) { + if let Some(span) = self.span() { + return visitor.visit_map(super::SpannedDeserializer::new(self, span)); + } + } + + self.deserialize_any(visitor) + } + serde::forward_to_deserialize_any! { bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq bytes byte_buf map option unit newtype_struct - ignored_any unit_struct tuple_struct tuple enum identifier struct + ignored_any unit_struct tuple_struct tuple enum identifier } } diff --git a/crates/toml_edit/src/de/inline_table.rs b/crates/toml_edit/src/de/inline_table.rs index becb3ea6..2155772d 100644 --- a/crates/toml_edit/src/de/inline_table.rs +++ b/crates/toml_edit/src/de/inline_table.rs @@ -34,13 +34,19 @@ impl<'de> serde::Deserializer<'de> for crate::InlineTable { fn deserialize_struct( self, - _name: &'static str, - _fields: &'static [&'static str], + name: &'static str, + fields: &'static [&'static str], visitor: V, ) -> Result where V: serde::de::Visitor<'de>, { + if super::is_spanned(name, fields) { + if let Some(span) = self.span() { + return visitor.visit_map(super::SpannedDeserializer::new(self, span)); + } + } + self.deserialize_any(visitor) } @@ -87,7 +93,7 @@ impl<'de> serde::de::IntoDeserializer<'de, crate::de::Error> for crate::InlineTa pub(crate) struct InlineTableMapAccess { iter: indexmap::map::IntoIter, span: Option>, - value: Option<(crate::InternalString, crate::Item)>, + value: Option, } impl InlineTableMapAccess { @@ -110,15 +116,16 @@ impl<'de> serde::de::MapAccess<'de> for InlineTableMapAccess { { match self.iter.next() { Some((k, v)) => { - let ret = seed.deserialize(k.into_deserializer()).map(Some).map_err( - |mut e: Self::Error| { + let ret = seed + .deserialize(super::KeyDeserializer::new(k, v.key.span())) + .map(Some) + .map_err(|mut e: Self::Error| { if e.span().is_none() { e.set_span(v.key.span()); } e - }, - ); - self.value = Some((k, v.value)); + }); + self.value = Some(v.value); ret } None => Ok(None), @@ -130,7 +137,7 @@ impl<'de> serde::de::MapAccess<'de> for InlineTableMapAccess { V: serde::de::DeserializeSeed<'de>, { match self.value.take() { - Some((_k, v)) => { + Some(v) => { let span = v.span(); seed.deserialize(crate::de::ItemDeserializer::new(v)) .map_err(|mut err| { diff --git a/crates/toml_edit/src/de/item.rs b/crates/toml_edit/src/de/item.rs index 238e59b1..ab8b60fd 100644 --- a/crates/toml_edit/src/de/item.rs +++ b/crates/toml_edit/src/de/item.rs @@ -66,6 +66,12 @@ impl<'de> serde::Deserializer<'de> for ItemDeserializer { where V: serde::de::Visitor<'de>, { + if super::is_spanned(name, fields) { + if let Some(span) = self.input.span() { + return visitor.visit_map(super::SpannedDeserializer::new(self.input, span)); + } + } + if self.validate_struct_keys { match &self.input { crate::Item::Table(values) => super::validate_struct_keys(&values.items, fields)?, @@ -99,6 +105,14 @@ impl<'de> serde::Deserializer<'de> for ItemDeserializer { } } +impl<'de> serde::de::IntoDeserializer<'de, crate::de::Error> for ItemDeserializer { + type Deserializer = Self; + + fn into_deserializer(self) -> Self { + self + } +} + impl<'de> serde::Deserializer<'de> for crate::Item { type Error = Error; @@ -168,6 +182,24 @@ impl<'de> serde::Deserializer<'de> for crate::Item { }) } + fn deserialize_struct( + self, + name: &'static str, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + if super::is_spanned(name, fields) { + if let Some(span) = self.span() { + return visitor.visit_map(super::SpannedDeserializer::new(self, span)); + } + } + + self.deserialize_any(visitor) + } + // Called when the type to deserialize is an enum, as opposed to a field in the type. fn deserialize_enum( self, @@ -217,7 +249,7 @@ impl<'de> serde::Deserializer<'de> for crate::Item { serde::forward_to_deserialize_any! { bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq - bytes byte_buf map unit struct + bytes byte_buf map unit ignored_any unit_struct tuple_struct tuple identifier } } diff --git a/crates/toml_edit/src/de/key.rs b/crates/toml_edit/src/de/key.rs new file mode 100644 index 00000000..1816ce76 --- /dev/null +++ b/crates/toml_edit/src/de/key.rs @@ -0,0 +1,140 @@ +use serde::de::IntoDeserializer; + +use super::Error; + +pub(crate) struct KeyDeserializer { + span: Option>, + key: crate::InternalString, +} + +impl KeyDeserializer { + pub(crate) fn new(key: crate::InternalString, span: Option>) -> Self { + KeyDeserializer { span, key } + } +} + +impl<'de> serde::de::IntoDeserializer<'de, Error> for KeyDeserializer { + type Deserializer = Self; + + fn into_deserializer(self) -> Self::Deserializer { + self + } +} + +impl<'de> serde::de::Deserializer<'de> for KeyDeserializer { + type Error = Error; + + fn deserialize_any(self, visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + self.key.into_deserializer().deserialize_any(visitor) + } + + fn deserialize_enum( + self, + name: &str, + variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + let _ = name; + let _ = variants; + visitor.visit_enum(self) + } + + fn deserialize_struct( + self, + name: &'static str, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + if super::is_spanned(name, fields) { + if let Some(span) = self.span.clone() { + return visitor.visit_map(super::SpannedDeserializer::new(self.key.as_str(), span)); + } + } + self.deserialize_any(visitor) + } + + serde::forward_to_deserialize_any! { + bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq + bytes byte_buf map option unit newtype_struct + ignored_any unit_struct tuple_struct tuple identifier + } +} + +impl<'de> serde::de::EnumAccess<'de> for KeyDeserializer { + type Error = super::Error; + type Variant = UnitOnly; + + fn variant_seed(self, seed: T) -> Result<(T::Value, Self::Variant), Self::Error> + where + T: serde::de::DeserializeSeed<'de>, + { + seed.deserialize(self).map(unit_only) + } +} + +pub(crate) struct UnitOnly { + marker: std::marker::PhantomData, +} + +fn unit_only(t: T) -> (T, UnitOnly) { + ( + t, + UnitOnly { + marker: std::marker::PhantomData, + }, + ) +} + +impl<'de, E> serde::de::VariantAccess<'de> for UnitOnly +where + E: serde::de::Error, +{ + type Error = E; + + fn unit_variant(self) -> Result<(), Self::Error> { + Ok(()) + } + + fn newtype_variant_seed(self, _seed: T) -> Result + where + T: serde::de::DeserializeSeed<'de>, + { + Err(serde::de::Error::invalid_type( + serde::de::Unexpected::UnitVariant, + &"newtype variant", + )) + } + + fn tuple_variant(self, _len: usize, _visitor: V) -> Result + where + V: serde::de::Visitor<'de>, + { + Err(serde::de::Error::invalid_type( + serde::de::Unexpected::UnitVariant, + &"tuple variant", + )) + } + + fn struct_variant( + self, + _fields: &'static [&'static str], + _visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + Err(serde::de::Error::invalid_type( + serde::de::Unexpected::UnitVariant, + &"struct variant", + )) + } +} diff --git a/crates/toml_edit/src/de/mod.rs b/crates/toml_edit/src/de/mod.rs index e45d8c1b..cfa81470 100644 --- a/crates/toml_edit/src/de/mod.rs +++ b/crates/toml_edit/src/de/mod.rs @@ -8,6 +8,8 @@ use serde::de::DeserializeOwned; mod array; mod inline_table; mod item; +mod key; +mod spanned; mod table; mod table_enum; mod value; @@ -15,6 +17,8 @@ mod value; use array::*; use inline_table::*; use item::*; +use key::*; +use spanned::*; use table::*; use table_enum::*; @@ -148,6 +152,18 @@ impl<'de> serde::Deserializer<'de> for Deserializer { self.input.deserialize_option(visitor) } + fn deserialize_struct( + self, + name: &'static str, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + self.input.deserialize_struct(name, fields, visitor) + } + // Called when the type to deserialize is an enum, as opposed to a field in the type. fn deserialize_enum( self, @@ -164,7 +180,7 @@ impl<'de> serde::Deserializer<'de> for Deserializer { serde::forward_to_deserialize_any! { bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq bytes byte_buf map unit newtype_struct - ignored_any unit_struct tuple_struct tuple identifier struct + ignored_any unit_struct tuple_struct tuple identifier } } @@ -199,6 +215,24 @@ impl<'de> serde::Deserializer<'de> for crate::Document { }) } + fn deserialize_struct( + self, + name: &'static str, + fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: serde::de::Visitor<'de>, + { + if is_spanned(name, fields) { + if let Some(span) = self.span() { + return visitor.visit_map(SpannedDeserializer::new(self, span)); + } + } + + self.deserialize_any(visitor) + } + // Called when the type to deserialize is an enum, as opposed to a field in the type. fn deserialize_enum( self, @@ -221,7 +255,7 @@ impl<'de> serde::Deserializer<'de> for crate::Document { serde::forward_to_deserialize_any! { bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq bytes byte_buf map unit newtype_struct - ignored_any unit_struct tuple_struct tuple identifier struct + ignored_any unit_struct tuple_struct tuple identifier } } diff --git a/crates/toml_edit/src/de/spanned.rs b/crates/toml_edit/src/de/spanned.rs new file mode 100644 index 00000000..0ebfb7fd --- /dev/null +++ b/crates/toml_edit/src/de/spanned.rs @@ -0,0 +1,74 @@ +use serde::de::value::BorrowedStrDeserializer; +use serde::de::IntoDeserializer as _; + +use super::Error; + +pub(crate) struct SpannedDeserializer<'de, T: serde::de::IntoDeserializer<'de, Error>> { + phantom_data: std::marker::PhantomData<&'de ()>, + start: Option, + end: Option, + value: Option, +} + +impl<'de, T> SpannedDeserializer<'de, T> +where + T: serde::de::IntoDeserializer<'de, Error>, +{ + pub(crate) fn new(value: T, span: std::ops::Range) -> Self { + Self { + phantom_data: Default::default(), + start: Some(span.start), + end: Some(span.end), + value: Some(value), + } + } +} + +impl<'de, T> serde::de::MapAccess<'de> for SpannedDeserializer<'de, T> +where + T: serde::de::IntoDeserializer<'de, Error>, +{ + type Error = Error; + fn next_key_seed(&mut self, seed: K) -> Result, Error> + where + K: serde::de::DeserializeSeed<'de>, + { + if self.start.is_some() { + seed.deserialize(BorrowedStrDeserializer::new(serde_spanned::START_FIELD)) + .map(Some) + } else if self.end.is_some() { + seed.deserialize(BorrowedStrDeserializer::new(serde_spanned::END_FIELD)) + .map(Some) + } else if self.value.is_some() { + seed.deserialize(BorrowedStrDeserializer::new(serde_spanned::VALUE_FIELD)) + .map(Some) + } else { + Ok(None) + } + } + + fn next_value_seed(&mut self, seed: V) -> Result + where + V: serde::de::DeserializeSeed<'de>, + { + if let Some(start) = self.start.take() { + seed.deserialize(start.into_deserializer()) + } else if let Some(end) = self.end.take() { + seed.deserialize(end.into_deserializer()) + } else if let Some(value) = self.value.take() { + seed.deserialize(value.into_deserializer()) + } else { + panic!("next_value_seed called before next_key_seed") + } + } +} + +pub(crate) fn is_spanned(name: &'static str, fields: &'static [&'static str]) -> bool { + name == serde_spanned::NAME + && fields + == [ + serde_spanned::START_FIELD, + serde_spanned::END_FIELD, + serde_spanned::VALUE_FIELD, + ] +} diff --git a/crates/toml_edit/src/de/table.rs b/crates/toml_edit/src/de/table.rs index fd5aca32..ba19796e 100644 --- a/crates/toml_edit/src/de/table.rs +++ b/crates/toml_edit/src/de/table.rs @@ -34,13 +34,19 @@ impl<'de> serde::Deserializer<'de> for crate::Table { fn deserialize_struct( self, - _name: &'static str, - _fields: &'static [&'static str], + name: &'static str, + fields: &'static [&'static str], visitor: V, ) -> Result where V: serde::de::Visitor<'de>, { + if super::is_spanned(name, fields) { + if let Some(span) = self.span() { + return visitor.visit_map(super::SpannedDeserializer::new(self, span)); + } + } + self.deserialize_any(visitor) } @@ -87,7 +93,7 @@ impl<'de> serde::de::IntoDeserializer<'de, crate::de::Error> for crate::Table { pub(crate) struct TableMapAccess { iter: indexmap::map::IntoIter, span: Option>, - value: Option<(crate::InternalString, crate::Item)>, + value: Option, } impl TableMapAccess { @@ -110,15 +116,16 @@ impl<'de> serde::de::MapAccess<'de> for TableMapAccess { { match self.iter.next() { Some((k, v)) => { - let ret = seed.deserialize(k.into_deserializer()).map(Some).map_err( - |mut e: Self::Error| { + let ret = seed + .deserialize(super::KeyDeserializer::new(k, v.key.span())) + .map(Some) + .map_err(|mut e: Self::Error| { if e.span().is_none() { e.set_span(v.key.span()); } e - }, - ); - self.value = Some((k, v.value)); + }); + self.value = Some(v.value); ret } None => Ok(None), @@ -130,7 +137,7 @@ impl<'de> serde::de::MapAccess<'de> for TableMapAccess { V: serde::de::DeserializeSeed<'de>, { match self.value.take() { - Some((_k, v)) => seed.deserialize(crate::de::ItemDeserializer::new(v)), + Some(v) => seed.deserialize(crate::de::ItemDeserializer::new(v)), None => { panic!("no more values in next_value_seed, internal error in ItemDeserializer") } diff --git a/crates/toml_edit/src/de/table_enum.rs b/crates/toml_edit/src/de/table_enum.rs index bea79d41..40cfe62a 100644 --- a/crates/toml_edit/src/de/table_enum.rs +++ b/crates/toml_edit/src/de/table_enum.rs @@ -82,7 +82,7 @@ impl<'de> serde::de::VariantAccess<'de> for TableEnumDeserializer { if tuple_values.len() == len { serde::de::Deserializer::deserialize_seq( - super::ArrayDeserializer::new(tuple_values), + super::ArrayDeserializer::new(tuple_values, values_span), visitor, ) } else { @@ -125,7 +125,7 @@ impl<'de> serde::de::VariantAccess<'de> for TableEnumDeserializer { if tuple_values.len() == len { serde::de::Deserializer::deserialize_seq( - super::ArrayDeserializer::new(tuple_values), + super::ArrayDeserializer::new(tuple_values, values_span), visitor, ) } else { diff --git a/crates/toml_edit/src/de/value.rs b/crates/toml_edit/src/de/value.rs index f24bc54c..1ffa2a0f 100644 --- a/crates/toml_edit/src/de/value.rs +++ b/crates/toml_edit/src/de/value.rs @@ -43,6 +43,12 @@ impl<'de> serde::Deserializer<'de> for crate::Value { } } + if super::is_spanned(name, fields) { + if let Some(span) = self.span() { + return visitor.visit_map(super::SpannedDeserializer::new(self, span)); + } + } + self.deserialize_any(visitor) } diff --git a/crates/toml_edit/src/easy/mod.rs b/crates/toml_edit/src/easy/mod.rs index 78c09e81..fbf081c1 100644 --- a/crates/toml_edit/src/easy/mod.rs +++ b/crates/toml_edit/src/easy/mod.rs @@ -147,5 +147,6 @@ pub use crate::toml; pub use de::{from_document, from_slice, from_str, Deserializer}; #[doc(no_inline)] pub use ser::{to_document, to_string, to_string_pretty, to_vec, Serializer}; +pub use serde_spanned::Spanned; #[doc(no_inline)] pub use value::Value; diff --git a/crates/toml_edit/tests/testsuite/main.rs b/crates/toml_edit/tests/testsuite/main.rs index afb1f328..16943b15 100644 --- a/crates/toml_edit/tests/testsuite/main.rs +++ b/crates/toml_edit/tests/testsuite/main.rs @@ -8,4 +8,6 @@ mod macros; mod parse; mod pretty; mod serde; +mod spanned; +mod spanned_impls; mod stackoverflow; diff --git a/crates/toml_edit/tests/testsuite/spanned.rs b/crates/toml_edit/tests/testsuite/spanned.rs new file mode 100644 index 00000000..db3e045b --- /dev/null +++ b/crates/toml_edit/tests/testsuite/spanned.rs @@ -0,0 +1,236 @@ +#![cfg(feature = "easy")] +#![allow(clippy::disallowed_names)] + +use std::collections::HashMap; +use std::fmt::Debug; + +use toml_edit::easy as toml; + +use serde::Deserialize; +use toml_edit::easy::value::Datetime; +use toml_edit::easy::Spanned; + +/// A set of good datetimes. +pub fn good_datetimes() -> Vec<&'static str> { + vec![ + "1997-09-09T09:09:09Z", + "1997-09-09T09:09:09+09:09", + "1997-09-09T09:09:09-09:09", + "1997-09-09T09:09:09", + "1997-09-09", + "09:09:09", + "1997-09-09T09:09:09.09Z", + "1997-09-09T09:09:09.09+09:09", + "1997-09-09T09:09:09.09-09:09", + "1997-09-09T09:09:09.09", + "09:09:09.09", + ] +} + +#[test] +fn test_spanned_field() { + #[derive(Deserialize)] + struct Foo { + foo: Spanned, + } + + #[derive(Deserialize)] + struct BareFoo { + foo: T, + } + + fn good(s: &str, expected: &str, end: Option) + where + T: serde::de::DeserializeOwned + Debug + PartialEq, + { + let foo: Foo = toml::from_str(s).unwrap(); + + assert_eq!(6, foo.foo.span().start); + if let Some(end) = end { + assert_eq!(end, foo.foo.span().end); + } else { + assert_eq!(s.len(), foo.foo.span().end); + } + assert_eq!(expected, &s[foo.foo.span()]); + + // Test for Spanned<> at the top level + let foo_outer: Spanned> = toml::from_str(s).unwrap(); + + assert_eq!(0, foo_outer.span().start); + assert_eq!(s.len(), foo_outer.span().end); + assert_eq!(foo.foo.into_inner(), foo_outer.into_inner().foo); + } + + good::("foo = \"foo\"", "\"foo\"", None); + good::("foo = 42", "42", None); + // leading plus + good::("foo = +42", "+42", None); + // table + good::>( + "foo = {\"foo\" = 42, \"bar\" = 42}", + "{\"foo\" = 42, \"bar\" = 42}", + None, + ); + // array + good::>("foo = [0, 1, 2, 3, 4]", "[0, 1, 2, 3, 4]", None); + // datetime + good::( + "foo = \"1997-09-09T09:09:09Z\"", + "\"1997-09-09T09:09:09Z\"", + None, + ); + + for expected in good_datetimes() { + let s = format!("foo = {}", expected); + good::(&s, expected, None); + } + // ending at something other than the absolute end + good::("foo = 42\nnoise = true", "42", Some(8)); +} + +#[test] +fn test_inner_spanned_table() { + #[derive(Deserialize)] + struct Foo { + foo: Spanned, Spanned>>, + } + + fn good(s: &str, zero: bool) { + let foo: Foo = toml::from_str(s).unwrap(); + + if zero { + assert_eq!(foo.foo.span().start, 0); + assert_eq!(foo.foo.span().end, 73); + } else { + assert_eq!(foo.foo.span().start, s.find('{').unwrap()); + assert_eq!(foo.foo.span().end, s.find('}').unwrap() + 1); + } + for (k, v) in foo.foo.as_ref().iter() { + assert_eq!(&s[k.span().start..k.span().end], k.as_ref()); + assert_eq!(&s[(v.span().start + 1)..(v.span().end - 1)], v.as_ref()); + } + } + + good( + "\ + [foo] + a = 'b' + bar = 'baz' + c = 'd' + e = \"f\" + ", + true, + ); + + good( + " + foo = { a = 'b', bar = 'baz', c = 'd', e = \"f\" }", + false, + ); +} + +#[test] +fn test_outer_spanned_table() { + #[derive(Deserialize)] + struct Foo { + foo: HashMap, Spanned>, + } + + fn good(s: &str) { + let foo: Foo = toml::from_str(s).unwrap(); + + for (k, v) in foo.foo.iter() { + assert_eq!(&s[k.span().start..k.span().end], k.as_ref()); + assert_eq!(&s[(v.span().start + 1)..(v.span().end - 1)], v.as_ref()); + } + } + + good( + " + [foo] + a = 'b' + bar = 'baz' + c = 'd' + e = \"f\" + ", + ); + + good( + " + foo = { a = 'b', bar = 'baz', c = 'd', e = \"f\" } + ", + ); +} + +#[test] +fn test_spanned_nested() { + #[derive(Deserialize)] + struct Foo { + foo: HashMap, HashMap, Spanned>>, + } + + fn good(s: &str) { + let foo: Foo = toml::from_str(s).unwrap(); + + for (k, v) in foo.foo.iter() { + assert_eq!(&s[k.span().start..k.span().end], k.as_ref()); + for (n_k, n_v) in v.iter() { + assert_eq!(&s[n_k.span().start..n_k.span().end], n_k.as_ref()); + assert_eq!( + &s[(n_v.span().start + 1)..(n_v.span().end - 1)], + n_v.as_ref() + ); + } + } + } + + good( + " + [foo.a] + a = 'b' + c = 'd' + e = \"f\" + [foo.bar] + baz = 'true' + ", + ); + + good( + " + [foo] + foo = { a = 'b', bar = 'baz', c = 'd', e = \"f\" } + bazz = {} + g = { h = 'i' } + ", + ); +} + +#[test] +fn test_spanned_array() { + #[derive(Deserialize)] + struct Foo { + foo: Vec, Spanned>>>, + } + + let toml = "\ + [[foo]] + a = 'b' + bar = 'baz' + c = 'd' + e = \"f\" + [[foo]] + a = 'c' + bar = 'baz' + c = 'g' + e = \"h\" + "; + let foo_list: Foo = toml::from_str(toml).unwrap(); + + for (foo, expected) in foo_list.foo.iter().zip([0..75, 84..159]) { + assert_eq!(foo.span(), expected); + for (k, v) in foo.as_ref().iter() { + assert_eq!(&toml[k.span().start..k.span().end], k.as_ref()); + assert_eq!(&toml[(v.span().start + 1)..(v.span().end - 1)], v.as_ref()); + } + } +} diff --git a/crates/toml_edit/tests/testsuite/spanned_impls.rs b/crates/toml_edit/tests/testsuite/spanned_impls.rs new file mode 100644 index 00000000..ceb21991 --- /dev/null +++ b/crates/toml_edit/tests/testsuite/spanned_impls.rs @@ -0,0 +1,43 @@ +#![cfg(feature = "easy")] + +use std::cmp::{Ord, Ordering, PartialOrd}; + +use serde::Deserialize; +use toml_edit::easy::{from_str, Spanned}; + +#[test] +fn test_spans_impls() { + #[derive(Deserialize)] + struct Foo { + bar: Spanned, + baz: Spanned, + } + let f: Foo = from_str( + " + bar = true + baz = \"yes\" + ", + ) + .unwrap(); + let g: Foo = from_str( + " + baz = \"yes\" + bar = true + ", + ) + .unwrap(); + assert!(f.bar.span() != g.bar.span()); + assert!(f.baz.span() != g.baz.span()); + + // test that eq still holds + assert_eq!(f.bar, g.bar); + assert_eq!(f.baz, g.baz); + + // test that Ord returns equal order + assert_eq!(f.bar.cmp(&g.bar), Ordering::Equal); + assert_eq!(f.baz.cmp(&g.baz), Ordering::Equal); + + // test that PartialOrd returns equal order + assert_eq!(f.bar.partial_cmp(&g.bar), Some(Ordering::Equal)); + assert_eq!(f.baz.partial_cmp(&g.baz), Some(Ordering::Equal)); +}