diff --git a/Cargo.toml b/Cargo.toml index ca0ab20..0202a32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,14 +17,17 @@ edition = "2021" # Implement `VariableMap` for `indexmap::IndexMap`. indexmap = ["dep:indexmap"] +# Enable serde Deserialize and Serialize implementations for templates. +serde = ["dep:serde"] + # Enable support for performing substitution in all string values of a JSON document. -json = ["dep:serde", "dep:serde_json"] +json = ["serde", "dep:serde_json"] # Enable support for performing substitution in all string values of a TOML document. -toml = ["dep:serde", "dep:toml"] +toml = ["serde", "dep:toml"] # Enable support for performing substitution in all string values of a YAML document. -yaml = ["dep:serde", "dep:serde_yaml"] +yaml = ["serde", "dep:serde_yaml"] # Preserve the order of fields in JSON objects and TOML tables (YAML always preserves the order). preserve-order = ["toml?/preserve_order", "serde_json?/preserve_order"] @@ -45,6 +48,7 @@ unicode-width = "0.1.9" assert2 = "0.3.6" subst = { path = ".", features = ["json", "toml", "yaml"] } serde = { version = "1.0.0", features = ["derive"] } +serde_test = "1.0.177" [package.metadata.docs.rs] all-features = true diff --git a/src/features/mod.rs b/src/features/mod.rs index caa3c13..e69545c 100644 --- a/src/features/mod.rs +++ b/src/features/mod.rs @@ -13,3 +13,8 @@ pub mod yaml; #[cfg(feature = "toml")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "toml")))] pub mod toml; + +// This module isn't expected, since it only defines trait implementations. +#[cfg(feature = "serde")] +#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "serde")))] +mod serde; diff --git a/src/features/serde.rs b/src/features/serde.rs new file mode 100644 index 0000000..cf980ff --- /dev/null +++ b/src/features/serde.rs @@ -0,0 +1,130 @@ +use serde::Deserialize; +use serde::Deserializer; +use serde::Serialize; +use serde::Serializer; +use serde::de::Error; + +use crate::{ByteTemplate, ByteTemplateBuf, Template, TemplateBuf}; + +impl Serialize for Template<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(self.source()) + } +} + +impl Serialize for TemplateBuf { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(self.as_template().source()) + } +} + +impl Serialize for ByteTemplate<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_bytes(self.source()) + } +} + +impl Serialize for ByteTemplateBuf { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_bytes(self.as_template().source()) + } +} + +impl<'de> Deserialize<'de> for Template<'de> { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let source: &'de str = Deserialize::deserialize(deserializer)?; + Self::from_str(source).map_err(D::Error::custom) + } +} + +impl<'de> Deserialize<'de> for TemplateBuf { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let source: String = Deserialize::deserialize(deserializer)?; + Self::from_string(source).map_err(D::Error::custom) + } +} + +impl<'de> Deserialize<'de> for ByteTemplate<'de> { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let source: &'de [u8] = Deserialize::deserialize(deserializer)?; + Self::from_slice(source).map_err(D::Error::custom) + } +} + +impl<'de> Deserialize<'de> for ByteTemplateBuf { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct ByteBufVisitor; + impl<'de> serde::de::Visitor<'de> for ByteBufVisitor { + type Value = Vec; + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(formatter, "bytes") + } + fn visit_bytes(self, v: &[u8]) -> Result { + Ok(v.to_vec()) + } + fn visit_byte_buf(self, v: Vec) -> Result { + Ok(v) + } + } + + let source = deserializer.deserialize_byte_buf(ByteBufVisitor)?; + Self::from_vec(source).map_err(D::Error::custom) + } +} + +#[cfg(test)] +mod test { + use crate::{ByteTemplate, ByteTemplateBuf, Template, TemplateBuf}; + use assert2::let_assert; + use serde_test::{assert_tokens, Token}; + + const SOURCE: &str = "Hello $name"; + + #[test] + fn template_ser_de() { + let_assert!(Ok(template) = Template::from_str(SOURCE)); + assert_tokens(&template, &[Token::BorrowedStr(SOURCE)]); + } + + #[test] + fn template_buf_ser_de() { + let_assert!(Ok(template) = TemplateBuf::from_string(SOURCE.into())); + assert_tokens(&template, &[Token::String(SOURCE)]); + } + + #[test] + fn byte_template_ser_de() { + let_assert!(Ok(template) = ByteTemplate::from_slice(SOURCE.as_bytes())); + assert_tokens(&template, &[Token::BorrowedBytes(SOURCE.as_bytes())]); + } + + #[test] + fn byte_template_buf_ser_de() { + let_assert!(Ok(template) = ByteTemplateBuf::from_vec(SOURCE.as_bytes().to_vec())); + serde_test::assert_tokens(&template, &[Token::ByteBuf(SOURCE.as_bytes())]); + } +} diff --git a/src/non_aliasing.rs b/src/non_aliasing.rs index ec5c51e..1ad38bc 100644 --- a/src/non_aliasing.rs +++ b/src/non_aliasing.rs @@ -2,8 +2,7 @@ use core::mem::MaybeUninit; /// Simple wrapper around [`MaybeUninit`] that guarantees the type is initialized. /// -/// This may sound odd, but the compiler can not assume that `MaybeUninit` is initialized, -/// but as far is the compiler is concerned, `MaybeUninit` is not keeping any references around, even if `T` would. +/// This may sound odd, but as far is the compiler is concerned, `MaybeUninit` is not keeping any references around, even if `T` would. /// So `NonAliasing` is not aliassing anything untill you call `inner()` to get your `&T` back. /// /// We use this to create a (hopefully) sound self referential struct in `TemplateBuf` and `ByteTemplateBuf`. @@ -35,3 +34,19 @@ impl Drop for NonAliasing { } } } + +impl core::cmp::Eq for NonAliasing {} + +impl core::cmp::PartialEq for NonAliasing { + fn eq(&self, other: &Self) -> bool { + self.inner() == other.inner() + } + + #[allow( + clippy::partialeq_ne_impl, + reason = "we don't know how T implements eq/ne, we just forward behavior" + )] + fn ne(&self, other: &Self) -> bool { + self.inner() != other.inner() + } +} diff --git a/src/template/mod.rs b/src/template/mod.rs index 6caedea..9e9e637 100644 --- a/src/template/mod.rs +++ b/src/template/mod.rs @@ -1,8 +1,8 @@ use core::pin::Pin; -use crate::VariableMap; use crate::error::{ExpandError, ParseError}; use crate::non_aliasing::NonAliasing; +use crate::VariableMap; mod raw; @@ -16,7 +16,7 @@ mod raw; /// /// If you have a byte slice or vector instead of a string, /// you can use [`ByteTemplate`] or [`ByteTemplateBuf`]. -#[derive(Clone)] +#[derive(Clone, Eq)] pub struct Template<'a> { source: &'a str, raw: raw::Template, @@ -29,6 +29,13 @@ impl std::fmt::Debug for Template<'_> { } } +impl std::cmp::PartialEq for Template<'_> { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.source == other.source + } +} + impl<'a> Template<'a> { /// Parse a template from a string slice. /// @@ -94,6 +101,7 @@ impl<'a> Template<'a> { /// /// If you have a byte slice or vector instead of a string, /// you can use [`ByteTemplate`] or [`ByteTemplateBuf`]. +#[derive(Eq)] pub struct TemplateBuf { // SAFETY: To avoid dangling references, Template must be dropped before // source, therefore the template field must be precede the source field. @@ -109,19 +117,13 @@ impl Clone for TemplateBuf { let source = self.source.clone(); let raw = self.template.inner().raw.clone(); - let template = Template { - raw, - source: &*source, - }; + let template = Template { raw, source: &source }; // SAFETY: The str slice given to `template` must remain valid. // Since `String` keeps data on the heap, it remains valid when the `source` is moved. // We MUST ensure we do not modify, drop or overwrite `source`. let template = unsafe { template.transmute_lifetime() }; let template = NonAliasing::new(template); - Self { - template, - source, - } + Self { template, source } } } @@ -134,6 +136,12 @@ impl std::fmt::Debug for TemplateBuf { } } +impl std::cmp::PartialEq for TemplateBuf { + fn eq(&self, other: &Self) -> bool { + self.as_template() == other.as_template() + } +} + impl TemplateBuf { /// Parse a template from a string. /// @@ -150,7 +158,7 @@ impl TemplateBuf { #[inline] pub fn from_string(source: String) -> Result { let source = Pin::new(source); - let template = Template::from_str(&*source)?; + let template = Template::from_str(&source)?; // SAFETY: The str slice given to `template` must remain valid. // Since `String` keeps data on the heap, it remains valid when the `source` is moved. @@ -160,6 +168,12 @@ impl TemplateBuf { Ok(Self { source, template }) } + /// Get the original source string. + #[inline] + pub fn source(&self) -> &str { + &self.source + } + /// Consume the template to get the original source string. #[inline] pub fn into_source(self) -> String { @@ -217,7 +231,7 @@ impl From> for TemplateBuf { let source: Pin = Pin::new(other.source.into()); let template = Template { - source: &*source, + source: &source, raw: other.raw, }; @@ -256,6 +270,15 @@ impl std::fmt::Debug for ByteTemplate<'_> { } } +impl std::cmp::PartialEq for ByteTemplate<'_> { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.source == other.source + } +} + +impl std::cmp::Eq for ByteTemplate<'_> {} + impl<'a> ByteTemplate<'a> { /// Parse a template from a byte slice. /// @@ -333,10 +356,7 @@ impl Clone for ByteTemplateBuf { let source = self.source.clone(); let raw = self.template.inner().raw.clone(); - let template = ByteTemplate { - raw, - source: &*source, - }; + let template = ByteTemplate { raw, source: &source }; // SAFETY: The slice given to `template` must remain valid. // Since `Pin>` keeps data on the heap, it remains valid when the `source` is moved. @@ -344,10 +364,7 @@ impl Clone for ByteTemplateBuf { let template = unsafe { template.transmute_lifetime() }; let template = NonAliasing::new(template); - Self { - template, - source, - } + Self { template, source } } } @@ -360,6 +377,15 @@ impl std::fmt::Debug for ByteTemplateBuf { } } +impl std::cmp::PartialEq for ByteTemplateBuf { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.as_template() == other.as_template() + } +} + +impl std::cmp::Eq for ByteTemplateBuf {} + impl ByteTemplateBuf { /// Parse a template from a vector of bytes. /// @@ -374,7 +400,7 @@ impl ByteTemplateBuf { #[inline] pub fn from_vec(source: Vec) -> Result { let source = Pin::new(source); - let template = ByteTemplate::from_slice(&*source)?; + let template = ByteTemplate::from_slice(&source)?; // SAFETY: The slice given to `template` must remain valid. // Since `Vec` keeps data on the heap, it remains valid when the `source` is moved. @@ -385,6 +411,12 @@ impl ByteTemplateBuf { Ok(Self { source, template }) } + /// Get the original source bytes. + #[inline] + pub fn source(&self) -> &[u8] { + &self.source + } + /// Consume the template to get the original source vector. #[inline] pub fn into_source(self) -> Vec { @@ -443,7 +475,7 @@ impl From> for ByteTemplateBuf { let source = Pin::new(source); let template = ByteTemplate { - source: &*source, + source: &source, raw: other.raw, }; @@ -463,7 +495,10 @@ impl std::fmt::Debug for DebugByteString<'_> { #[inline] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if let Ok(data) = std::str::from_utf8(self.0) { - write!(f, "b{:?}", data) + #[allow(clippy::uninlined_format_args, reason = "this is not worth an MSRV bump")] + { + write!(f, "b{:?}", data) + } } else { std::fmt::Debug::fmt(self.0, f) } diff --git a/src/template/raw/mod.rs b/src/template/raw/mod.rs index 184d454..5315680 100644 --- a/src/template/raw/mod.rs +++ b/src/template/raw/mod.rs @@ -4,14 +4,14 @@ mod parse; /// Raw template that doesn't know track the original source. /// /// Internally, this keeps a bunch of offsets into the original source. -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq)] pub struct Template { /// The individual parts that make up the template. parts: Vec, } /// One piece of a parsed template. -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq)] pub enum Part { /// A literal string to be used verbatim from the original source. Literal(Literal), @@ -24,7 +24,7 @@ pub enum Part { } /// A literal string to be used verbatim from the original source. -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq)] pub struct Literal { /// The range of the literal in the original source. /// @@ -35,7 +35,7 @@ pub struct Literal { } /// An escaped byte. -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct EscapedByte { /// The escaped byte. /// @@ -44,7 +44,7 @@ pub struct EscapedByte { } /// A variable to be substituted at expansion time. -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq)] pub struct Variable { /// The range in the source defining the name of the variable. ///