Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,22 @@ edition = "2021"
indexmap = ["dep:indexmap"]

# 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"]

# Enable #[doc(cfg...)] annotations for optional parts of the library (requires a nightly compiler).
doc-cfg = []

serde = [ "dep:serde" ]

[dependencies]
indexmap = { version = "2.5.0", optional = true }
memchr = "2.4.1"
Expand All @@ -45,6 +47,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
5 changes: 5 additions & 0 deletions src/features/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
219 changes: 219 additions & 0 deletions src/features/serde.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
use std::marker::PhantomData;

use serde::{
de::{Error, Visitor},
Deserialize,
Deserializer,
Serialize,
Serializer,
};

use crate::{ByteTemplate, ByteTemplateBuf, Template, TemplateBuf};

struct TemplateVisitor<'de> {
_lifetime: PhantomData<&'de ()>,
}

impl<'de> TemplateVisitor<'de> {
const fn new() -> Self {
Self { _lifetime: PhantomData }
}
}

impl<'de> Visitor<'de> for TemplateVisitor<'de> {
type Value = Template<'de>;

fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
where
E: Error,
{
Template::from_str(v).map_err(E::custom)
}

fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a borrowed string")
}
}

struct TemplateBufVisitor;

impl<'de> Visitor<'de> for TemplateBufVisitor {
type Value = TemplateBuf;

fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a string")
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: Error,
{
self.visit_string(v.to_owned())
}

fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: Error,
{
TemplateBuf::from_string(v).map_err(E::custom)
}
}

struct ByteTemplateVisitor<'de> {
_lifetime: PhantomData<&'de ()>,
}

impl<'de> ByteTemplateVisitor<'de> {
const fn new() -> Self {
Self { _lifetime: PhantomData }
}
}

impl<'de> Visitor<'de> for ByteTemplateVisitor<'de> {
type Value = ByteTemplate<'de>;

fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E>
where
E: Error,
{
ByteTemplate::from_slice(v).map_err(E::custom)
}

fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a borrowed string")
}
}

struct ByteTemplateBufVisitor;

impl<'de> Visitor<'de> for ByteTemplateBufVisitor {
type Value = ByteTemplateBuf;

fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a string")
}

fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: Error,
{
self.visit_byte_buf(v.to_vec())
}

fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
where
E: Error,
{
ByteTemplateBuf::from_vec(v).map_err(E::custom)
}
}

impl Serialize for Template<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.source())
}
}

impl Serialize for TemplateBuf {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_template().source())
}
}

impl Serialize for ByteTemplate<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bytes(self.source())
}
}

impl Serialize for ByteTemplateBuf {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bytes(self.as_template().source())
}
}

impl<'de> Deserialize<'de> for Template<'de> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(TemplateVisitor::new())
}
}

impl<'de> Deserialize<'de> for TemplateBuf {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_string(TemplateBufVisitor)
}
}

impl<'de> Deserialize<'de> for ByteTemplate<'de> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_bytes(ByteTemplateVisitor::new())
}
}

impl<'de> Deserialize<'de> for ByteTemplateBuf {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_string(ByteTemplateBufVisitor)
}
}

#[cfg(test)]
mod test {
use serde_test::{assert_tokens, Token};

use crate::{ByteTemplate, ByteTemplateBuf, Template, TemplateBuf};

const STR_SOURCE: &str = "{hello}";
const BYTE_SOURCE: &[u8] = b"{hello}";

#[test]
fn template_ser_de() {
let template = Template::from_str(STR_SOURCE).unwrap();

assert_tokens(&template, &[Token::BorrowedStr(STR_SOURCE)]);
}

#[test]
fn template_buf_ser_de() {
let template = TemplateBuf::from_string(STR_SOURCE.to_string()).unwrap();

assert_tokens(&template, &[Token::String(STR_SOURCE)]);
}

#[test]
fn byte_template_ser_de() {
let template = ByteTemplate::from_slice(BYTE_SOURCE).unwrap();

assert_tokens(&template, &[Token::BorrowedBytes(BYTE_SOURCE)]);
}

#[test]
fn byte_template_buf_ser_de() {
let template = ByteTemplateBuf::from_vec(BYTE_SOURCE.to_vec()).unwrap();

assert_tokens(&template, &[Token::ByteBuf(BYTE_SOURCE)]);
}
}
57 changes: 40 additions & 17 deletions src/template/mod.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -29,6 +29,15 @@ impl std::fmt::Debug for Template<'_> {
}
}

impl std::cmp::PartialEq for Template<'_> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.source == other.source
}
}

impl std::cmp::Eq for Template<'_> {}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Eq can also be derived.


impl<'a> Template<'a> {
/// Parse a template from a string slice.
///
Expand Down Expand Up @@ -109,19 +118,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 }
}
}

Expand All @@ -134,6 +137,14 @@ 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 std::cmp::Eq for TemplateBuf {}

impl TemplateBuf {
/// Parse a template from a string.
///
Expand Down Expand Up @@ -256,6 +267,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.
///
Expand Down Expand Up @@ -333,21 +353,15 @@ 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<Vec<u8>>` 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 }
}
}

Expand All @@ -360,6 +374,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.
///
Expand Down
Loading
Loading