diff --git a/Cargo.toml b/Cargo.toml index 57d480c..a4dcc32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,9 +17,9 @@ all-features = true [features] default = ["std"] -std = ["serde/std", "serde_bytes/std"] +std = ["serde/std"] unstable = [] -serde = ["dep:serde", "dep:serde_bytes"] +serde = ["dep:serde"] bstr = ["dep:bstr"] [dev-dependencies] @@ -44,12 +44,6 @@ optional = true default-features = false features = ["alloc"] -[dependencies.serde_bytes] -version = "0.11.3" -optional = true -default-features = false -features = ["alloc"] - [target.'cfg(loom)'.dependencies] loom = "0.7" diff --git a/src/bytes/serde.rs b/src/bytes/serde.rs index ec8d34c..a830981 100644 --- a/src/bytes/serde.rs +++ b/src/bytes/serde.rs @@ -1,10 +1,48 @@ +//! `serde` support for `HipByt`. +//! +//! This module provides support for serializing and deserializing `HipByt` +//! using [`serde`]. It is enabled by default when the `serde` feature is +//! enabled. +//! +//! # Examples +//! +//! ``` +//! use hipstr::HipByt; +//! +//! let s = HipByt::from(b"hello"); +//! let serialized = serde_json::to_string(&s).unwrap(); +//! assert_eq!(serialized, "[104,101,108,108,111]"); +//! +//! let deserialized: HipByt = serde_json::from_str(r#""hello""#).unwrap(); +//! assert_eq!(deserialized, s); +//! ``` +//! +//! # Notable aspects of the implementations +//! +//! Unlike the `Vec` generic implementation which treats data as a generic +//! sequence, `HipByt` uses the more efficient byte sequence specific API for +//! serialization (similar to the [`serde_bytes`] crate). Note that the actual +//! outcome of a serialization depends on the underlying format's support for +//! byte sequences. +//! +//! During deserialization, this implementation minimizes allocations by reusing +//! the deserializer's internal buffer if possible. +//! +//! [`serde_bytes`]: https://docs.rs/serde_bytes + +use core::marker::PhantomData; + +use serde::de::Visitor; use serde::{Deserialize, Serialize}; use super::HipByt; -use crate::alloc::borrow::Cow; +use crate::alloc::fmt; +use crate::alloc::string::String; use crate::alloc::vec::Vec; use crate::Backend; +const EXPECTING: &str = "a byte array"; + impl Serialize for HipByt<'_, B> where B: Backend, @@ -17,6 +55,59 @@ where } } +/// Deserializer's visitor for owned `HipByt`. +struct OwnedVisitor<'borrow, B: Backend>(PhantomData>); + +impl<'de, 'borrow, B: Backend> Visitor<'de> for OwnedVisitor<'borrow, B> { + type Value = HipByt<'borrow, B>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str(EXPECTING) + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: serde::de::Error, + { + Ok(HipByt::from(v)) + } + + fn visit_byte_buf(self, v: Vec) -> Result + where + E: serde::de::Error, + { + Ok(HipByt::from(v)) + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + Ok(HipByt::from(v.as_bytes())) + } + + fn visit_string(self, v: String) -> Result + where + E: serde::de::Error, + { + Ok(HipByt::from(v.into_bytes())) + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let len = core::cmp::min(seq.size_hint().unwrap_or(0), 4096); + let mut bytes = Vec::with_capacity(len); + + while let Some(b) = seq.next_element()? { + bytes.push(b); + } + + Ok(HipByt::from(bytes)) + } +} + impl<'de, B> Deserialize<'de> for HipByt<'_, B> where B: Backend, @@ -25,8 +116,74 @@ where where D: serde::Deserializer<'de>, { - let v: Vec = serde_bytes::deserialize(deserializer)?; - Ok(Self::from(v)) + deserializer.deserialize_bytes(OwnedVisitor(PhantomData)) + } +} + +/// Deserializer's visitor for borrowed `HipByt`. +struct BorrowedVisitor<'de, B: Backend>(PhantomData>); + +impl<'de, B: Backend> Visitor<'de> for BorrowedVisitor<'de, B> { + type Value = HipByt<'de, B>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str(EXPECTING) + } + + fn visit_borrowed_bytes(self, v: &'de [u8]) -> Result + where + E: serde::de::Error, + { + Ok(HipByt::borrowed(v)) + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: serde::de::Error, + { + Ok(HipByt::from(v)) + } + + fn visit_byte_buf(self, v: Vec) -> Result + where + E: serde::de::Error, + { + Ok(HipByt::from(v)) + } + + fn visit_borrowed_str(self, v: &'de str) -> Result + where + E: serde::de::Error, + { + Ok(HipByt::borrowed(v.as_bytes())) + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + Ok(HipByt::from(v.as_bytes())) + } + + fn visit_string(self, v: String) -> Result + where + E: serde::de::Error, + { + Ok(HipByt::from(v.into_bytes())) + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let len = core::cmp::min(seq.size_hint().unwrap_or(0), 4096); + let mut bytes = Vec::with_capacity(len); + + while let Some(b) = seq.next_element()? { + bytes.push(b); + } + + Ok(HipByt::from(bytes)) } } @@ -62,8 +219,7 @@ where D: serde::Deserializer<'de>, B: Backend, { - let cow: Cow<'de, [u8]> = serde_bytes::Deserialize::deserialize(deserializer)?; - Ok(HipByt::from(cow)) + deserializer.deserialize_bytes(BorrowedVisitor(PhantomData)) } #[cfg(test)] diff --git a/src/bytes/serde/tests.rs b/src/bytes/serde/tests.rs index 9733482..02e83f2 100644 --- a/src/bytes/serde/tests.rs +++ b/src/bytes/serde/tests.rs @@ -41,7 +41,7 @@ fn test_serde() { fn test_de_error() { assert_de_tokens_error::( &[Token::Bool(true)], - "invalid type: boolean `true`, expected byte array", + "invalid type: boolean `true`, expected a byte array", ); } diff --git a/src/os_string.rs b/src/os_string.rs index a60990e..68efd74 100644 --- a/src/os_string.rs +++ b/src/os_string.rs @@ -17,7 +17,7 @@ mod convert; // OsStr(ing) implements Serialize/Deserialize only on Unix and Windows. thx @dsherret #[cfg(all(feature = "serde", any(unix, windows)))] -mod serde; +pub mod serde; #[cfg(test)] mod tests; diff --git a/src/os_string/serde.rs b/src/os_string/serde.rs index 9345465..b1eed1b 100644 --- a/src/os_string/serde.rs +++ b/src/os_string/serde.rs @@ -1,3 +1,27 @@ +//! `serde` support for `HipOsStr`. +//! +//! This module provides support for serializing and deserializing `HipStr` +//! using [`serde`]. It is enabled by default when the `serde` feature is +//! enabled and on supported platforms (`unix` and `windows`). +//! +//! # Examples +//! +//! ``` +//! use hipstr::HipStr; +//! +//! let s = HipStr::from("hello"); +//! let serialized = serde_json::to_string(&s).unwrap(); +//! assert_eq!(serialized, r#""hello""#); +//! +//! let deserialized: HipStr = serde_json::from_str(&serialized).unwrap(); +//! assert_eq!(deserialized, s); +//! ``` +//! +//! # Notable aspects of the implementation +//! +//! Due to the overall weirdness of `OsString` and their support in `serde`, no +//! attempt is made to improve on `OsString` standard `serde` implementation. + use std::ffi::OsString; use serde::{Deserialize, Serialize}; diff --git a/src/path/serde.rs b/src/path/serde.rs index 026e2b6..6eaf3aa 100644 --- a/src/path/serde.rs +++ b/src/path/serde.rs @@ -1,10 +1,35 @@ -use std::path::PathBuf; +//! `serde` support for `HipPath`. +//! +//! This module provides support for serializing and deserializing `HipStr` +//! using [`serde`]. It is enabled by default when the `serde` feature is +//! enabled. +//! +//! # Examples +//! +//! ``` +//! use hipstr::HipPath; +//! +//! let s = HipPath::borrowed("/usr/bin"); +//! let serialized = serde_json::to_string(&s).unwrap(); +//! assert_eq!(serialized, r#""/usr/bin""#); +//! +//! let deserialized: HipPath = serde_json::from_str(&serialized).unwrap(); +//! assert_eq!(deserialized, s); +//! ``` +//! +//! # Notable aspects of the implementation +//! +//! During deserialization, this implementation minimizes allocations by reusing +//! the deserializer's internal buffer if possible. +//! +//! Unlike `PathBuf`'s `Deserialize`, this implementation declares transparently +//! that it's expecting a string. Indeed not reusing `HipStr`'s implementation +//! just does not make any sense. -use serde::{de, Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use super::HipPath; -use crate::alloc::borrow::Cow; -use crate::alloc::fmt; +use crate::string::HipStr; use crate::Backend; impl Serialize for HipPath<'_, B> @@ -29,39 +54,8 @@ where where D: serde::Deserializer<'de>, { - Ok(Self::from(PathBuf::deserialize(deserializer)?)) - } -} - -/// Minimal string cow visitor -struct CowVisitor; - -impl<'de> de::Visitor<'de> for CowVisitor { - type Value = Cow<'de, str>; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("path string") - } - - fn visit_borrowed_str(self, v: &'de str) -> Result - where - E: de::Error, - { - Ok(Cow::Borrowed(v)) - } - - fn visit_str(self, v: &str) -> Result - where - E: de::Error, - { - Ok(Cow::Owned(v.to_string())) - } - - fn visit_string(self, v: String) -> Result - where - E: de::Error, - { - Ok(Cow::Owned(v)) + let s = HipStr::deserialize(deserializer)?; + Ok(Self::from(s)) } } @@ -96,8 +90,7 @@ where D: serde::Deserializer<'de>, B: Backend, { - let cow: Cow<'de, str> = deserializer.deserialize_str(CowVisitor)?; - Ok(HipPath::from(cow)) + crate::string::serde::borrow_deserialize(deserializer).map(HipPath::from) } #[cfg(test)] diff --git a/src/path/serde/tests.rs b/src/path/serde/tests.rs index 3ce4728..8dc6bd4 100644 --- a/src/path/serde/tests.rs +++ b/src/path/serde/tests.rs @@ -19,7 +19,7 @@ fn test_serde() { fn test_serde_err() { assert_de_tokens_error::( &[Token::I32(0)], - "invalid type: integer `0`, expected path string", + "invalid type: integer `0`, expected a string", ); } @@ -94,6 +94,6 @@ fn test_serde_borrow_err() { Token::I32(0), Token::StructEnd, ], - "invalid type: integer `0`, expected path string", + "invalid type: integer `0`, expected a string", ); } diff --git a/src/string/serde.rs b/src/string/serde.rs index 41bc118..83828be 100644 --- a/src/string/serde.rs +++ b/src/string/serde.rs @@ -1,14 +1,40 @@ +//! `serde` support for `HipStr`. +//! +//! This module provides support for serializing and deserializing `HipStr` +//! using [`serde`]. It is enabled by default when the `serde` feature is +//! enabled. +//! +//! # Examples +//! +//! ``` +//! use hipstr::HipStr; +//! +//! let s = HipStr::from("hello"); +//! let serialized = serde_json::to_string(&s).unwrap(); +//! assert_eq!(serialized, r#""hello""#); +//! +//! let deserialized: HipStr = serde_json::from_str(&serialized).unwrap(); +//! assert_eq!(deserialized, s); +//! ``` +//! +//! # Notable aspects of the implementation +//! +//! During deserialization, this implementation minimizes allocations by +//! reusing the deserializer's internal buffer if possible. + +use core::fmt; use core::marker::PhantomData; use serde::de::{Error, Visitor}; use serde::{de, Deserialize, Deserializer, Serialize}; use super::HipStr; -use crate::alloc::borrow::Cow; -use crate::alloc::fmt; -use crate::alloc::string::{String, ToString}; +use crate::alloc::string::String; +use crate::bytes::HipByt; use crate::Backend; +const EXPECTING: &str = "a string"; + impl Serialize for HipStr<'_, B> where B: Backend, @@ -22,15 +48,14 @@ where } } -struct HipStrVisitor<'b, B> { - data: PhantomData<&'b B>, -} +/// Deserializer's visitor for owned `HipStr`. +struct OwnedVisitor<'borrow, B: Backend>(PhantomData>); -impl<'a, 'b, B: Backend> Visitor<'a> for HipStrVisitor<'b, B> { +impl<'a, 'b, B: Backend> Visitor<'a> for OwnedVisitor<'b, B> { type Value = HipStr<'b, B>; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a string") + formatter.write_str(EXPECTING) } #[inline] @@ -59,41 +84,39 @@ where where D: Deserializer<'de>, { - deserializer.deserialize_str(HipStrVisitor:: { - data: PhantomData::default(), - }) + deserializer.deserialize_str(OwnedVisitor(PhantomData)) } } -/// Minimal string cow visitor -struct CowVisitor; +/// Deserializer's visitor for borrowed `HipStr`. +struct BorrowedVisitor<'de, B: Backend>(PhantomData>); -impl<'de> de::Visitor<'de> for CowVisitor { - type Value = Cow<'de, str>; +impl<'de, B: Backend> Visitor<'de> for BorrowedVisitor<'de, B> { + type Value = HipStr<'de, B>; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a string") + formatter.write_str(EXPECTING) } fn visit_borrowed_str(self, v: &'de str) -> Result where E: de::Error, { - Ok(Cow::Borrowed(v)) + Ok(HipStr::borrowed(v)) } fn visit_str(self, v: &str) -> Result where E: de::Error, { - Ok(Cow::Owned(v.to_string())) + Ok(HipStr::from(v)) } fn visit_string(self, v: String) -> Result where E: de::Error, { - Ok(Cow::Owned(v)) + Ok(HipStr::from(v)) } } @@ -128,8 +151,7 @@ where D: serde::Deserializer<'de>, B: Backend, { - let cow: Cow<'de, str> = deserializer.deserialize_str(CowVisitor)?; - Ok(HipStr::from(cow)) + deserializer.deserialize_str(BorrowedVisitor(PhantomData)) } #[cfg(test)]