diff --git a/CHANGELOG.md b/CHANGELOG.md index f0fa068f..19f6e6b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Breaking +- MSRV is now `1.65.0`. + ### Added - Support for optional package `defmt` which allows for easy conversion for error types when using tools like `probe-rs` for logging over debuggers. - Implement `Serializer::collect_str` - Derive `Serialize` for `de::Error` and `ser::Error` +- Support for deserializing escaped strings. ### Changed diff --git a/src/de/mod.rs b/src/de/mod.rs index ad2145d5..88e8fc2f 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -201,6 +201,7 @@ impl<'a, 's> Deserializer<'a, 's> { } } + /// Parse a string, returning the escaped string. fn parse_str(&mut self) -> Result<&'a str> { if self.parse_whitespace().ok_or(Error::EofWhileParsingValue)? == b'"' { self.eat_char(); @@ -601,7 +602,10 @@ impl<'a, 'de, 's> de::Deserializer<'de> for &'a mut Deserializer<'de, 's> { where V: Visitor<'de>, { + // If the newtype struct is an `EscapedStr`... if name == crate::str::EscapedStr::NAME { + // ...deserialize as an escaped string instead. + struct EscapedStringDeserializer<'a, 'de, 's>(&'a mut Deserializer<'de, 's>); impl<'a, 'de, 's> serde::Deserializer<'de> for EscapedStringDeserializer<'a, 'de, 's> { @@ -611,9 +615,13 @@ impl<'a, 'de, 's> de::Deserializer<'de> for &'a mut Deserializer<'de, 's> { where V: Visitor<'de>, { + // The only structure which is deserialized at this point is an `EscapedStr`, + // so pass the escaped string to its implementation of visit_borrowed_str. + // This line defacto becomes `Ok(EscapedStr(self.0.parse_str()?))`. visitor.visit_borrowed_str(self.0.parse_str()?) } + // `EscapedStr` only deserializes strings, so we might as well forward all methods to `deserialize_any`. serde::forward_to_deserialize_any! { bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string bytes byte_buf option unit unit_struct newtype_struct seq tuple diff --git a/src/lib.rs b/src/lib.rs index c2149005..c40dbb44 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,10 +55,10 @@ //! This crate is guaranteed to compile on stable Rust 1.65.0 and up. It *might* compile with older //! versions but that may change in any new patch release. -// #![deny(missing_docs)] +#![deny(missing_docs)] #![deny(rust_2018_compatibility)] #![deny(rust_2018_idioms)] -// #![deny(warnings)] +#![deny(warnings)] #![cfg_attr(not(feature = "std"), no_std)] pub mod de; diff --git a/src/ser/mod.rs b/src/ser/mod.rs index 1c9f0f88..74db6e09 100644 --- a/src/ser/mod.rs +++ b/src/ser/mod.rs @@ -367,7 +367,10 @@ impl<'a, 'b: 'a> ser::Serializer for &'a mut Serializer<'b> { where T: ser::Serialize + ?Sized, { + // If the newtype struct is an `EscapedStr`... if name == crate::str::EscapedStr::NAME { + // serialize it as an already escaped string. + struct EscapedStringSerializer<'a, 'b>(&'a mut Serializer<'b>); impl<'a, 'b: 'a> serde::Serializer for EscapedStringSerializer<'a, 'b> { diff --git a/src/str.rs b/src/str.rs index dcfd5318..ab122485 100644 --- a/src/str.rs +++ b/src/str.rs @@ -1,14 +1,37 @@ +//! Utilities for serializing and deserializing strings. + +use core::fmt; + #[derive(Debug)] +/// A fragment of an escaped string pub enum EscapedStringFragment<'a> { + /// A series of characters which weren't escaped in the input. NotEscaped(&'a str), + /// A character which was escaped in the input. Escaped(char), } #[derive(Debug)] +/// Errors occuring while unescaping strings. pub enum StringUnescapeError { + /// Failed to unescape a character due to an invalid escape sequence. InvalidEscapeSequence, } +impl fmt::Display for StringUnescapeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + StringUnescapeError::InvalidEscapeSequence => write!( + f, + "Failed to unescape a character due to an invalid escape sequence." + ), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for StringUnescapeError {} + fn unescape_next_fragment( escaped_string: &str, ) -> Result<(EscapedStringFragment<'_>, &str), StringUnescapeError> { @@ -55,7 +78,23 @@ fn unescape_next_fragment( }) } -/// A borrowed escaped string +/// A borrowed escaped string. `EscapedStr` can be used to borrow an escaped string from the input, +/// even when deserialized using `from_str_escaped` or `from_slice_escaped`. +/// +/// ``` +/// #[derive(serde::Deserialize)] +/// struct Event<'a> { +/// name: heapless::String<16>, +/// #[serde(borrow)] +/// description: serde_json_core::str::EscapedStr<'a>, +/// } +/// +/// serde_json_core::de::from_str_escaped::>( +/// r#"{ "name": "Party\u0021", "description": "I'm throwing a party! Hopefully the \u2600 shines!" }"#, +/// &mut [0; 8], +/// ) +/// .unwrap(); +/// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(rename = "__serde_json_core_escaped_string__")] pub struct EscapedStr<'a>(pub &'a str); @@ -63,14 +102,17 @@ pub struct EscapedStr<'a>(pub &'a str); impl<'a> EscapedStr<'a> { pub(crate) const NAME: &'static str = "__serde_json_core_escaped_string__"; + /// Returns an iterator over the `EscapedStringFragment`s of an escaped string. pub fn fragments(&self) -> EscapedStringFragmentIter<'a> { EscapedStringFragmentIter(self.0) } } +/// An iterator over the `EscapedStringFragment`s of an escaped string. pub struct EscapedStringFragmentIter<'a>(&'a str); impl<'a> EscapedStringFragmentIter<'a> { + /// Views the underlying data as a subslice of the original data. pub fn as_str(&self) -> EscapedStr<'a> { EscapedStr(self.0) }