Skip to content

Commit

Permalink
Documented escaped string deserialization.
Browse files Browse the repository at this point in the history
  • Loading branch information
sammhicks committed Aug 6, 2024
1 parent fb0b8da commit 9f6af47
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 3 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 8 additions & 0 deletions src/de/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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> {
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions src/ser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down
44 changes: 43 additions & 1 deletion src/str.rs
Original file line number Diff line number Diff line change
@@ -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> {
Expand Down Expand Up @@ -55,22 +78,41 @@ 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::<Event<'_>>(
/// 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);

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)
}
Expand Down

0 comments on commit 9f6af47

Please sign in to comment.