Skip to content

Commit 9f6af47

Browse files
committed
Documented escaped string deserialization.
1 parent fb0b8da commit 9f6af47

File tree

5 files changed

+60
-3
lines changed

5 files changed

+60
-3
lines changed

Diff for: CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
77

88
## [Unreleased]
99

10+
### Breaking
11+
- MSRV is now `1.65.0`.
12+
1013
### Added
1114

1215
- Support for optional package `defmt` which allows for easy conversion for
1316
error types when using tools like `probe-rs` for logging over debuggers.
1417
- Implement `Serializer::collect_str`
1518
- Derive `Serialize` for `de::Error` and `ser::Error`
19+
- Support for deserializing escaped strings.
1620

1721
### Changed
1822

Diff for: src/de/mod.rs

+8
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ impl<'a, 's> Deserializer<'a, 's> {
201201
}
202202
}
203203

204+
/// Parse a string, returning the escaped string.
204205
fn parse_str(&mut self) -> Result<&'a str> {
205206
if self.parse_whitespace().ok_or(Error::EofWhileParsingValue)? == b'"' {
206207
self.eat_char();
@@ -601,7 +602,10 @@ impl<'a, 'de, 's> de::Deserializer<'de> for &'a mut Deserializer<'de, 's> {
601602
where
602603
V: Visitor<'de>,
603604
{
605+
// If the newtype struct is an `EscapedStr`...
604606
if name == crate::str::EscapedStr::NAME {
607+
// ...deserialize as an escaped string instead.
608+
605609
struct EscapedStringDeserializer<'a, 'de, 's>(&'a mut Deserializer<'de, 's>);
606610

607611
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> {
611615
where
612616
V: Visitor<'de>,
613617
{
618+
// The only structure which is deserialized at this point is an `EscapedStr`,
619+
// so pass the escaped string to its implementation of visit_borrowed_str.
620+
// This line defacto becomes `Ok(EscapedStr(self.0.parse_str()?))`.
614621
visitor.visit_borrowed_str(self.0.parse_str()?)
615622
}
616623

624+
// `EscapedStr` only deserializes strings, so we might as well forward all methods to `deserialize_any`.
617625
serde::forward_to_deserialize_any! {
618626
bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
619627
bytes byte_buf option unit unit_struct newtype_struct seq tuple

Diff for: src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@
5555
//! This crate is guaranteed to compile on stable Rust 1.65.0 and up. It *might* compile with older
5656
//! versions but that may change in any new patch release.
5757
58-
// #![deny(missing_docs)]
58+
#![deny(missing_docs)]
5959
#![deny(rust_2018_compatibility)]
6060
#![deny(rust_2018_idioms)]
61-
// #![deny(warnings)]
61+
#![deny(warnings)]
6262
#![cfg_attr(not(feature = "std"), no_std)]
6363

6464
pub mod de;

Diff for: src/ser/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,10 @@ impl<'a, 'b: 'a> ser::Serializer for &'a mut Serializer<'b> {
367367
where
368368
T: ser::Serialize + ?Sized,
369369
{
370+
// If the newtype struct is an `EscapedStr`...
370371
if name == crate::str::EscapedStr::NAME {
372+
// serialize it as an already escaped string.
373+
371374
struct EscapedStringSerializer<'a, 'b>(&'a mut Serializer<'b>);
372375

373376
impl<'a, 'b: 'a> serde::Serializer for EscapedStringSerializer<'a, 'b> {

Diff for: src/str.rs

+43-1
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,37 @@
1+
//! Utilities for serializing and deserializing strings.
2+
3+
use core::fmt;
4+
15
#[derive(Debug)]
6+
/// A fragment of an escaped string
27
pub enum EscapedStringFragment<'a> {
8+
/// A series of characters which weren't escaped in the input.
39
NotEscaped(&'a str),
10+
/// A character which was escaped in the input.
411
Escaped(char),
512
}
613

714
#[derive(Debug)]
15+
/// Errors occuring while unescaping strings.
816
pub enum StringUnescapeError {
17+
/// Failed to unescape a character due to an invalid escape sequence.
918
InvalidEscapeSequence,
1019
}
1120

21+
impl fmt::Display for StringUnescapeError {
22+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23+
match self {
24+
StringUnescapeError::InvalidEscapeSequence => write!(
25+
f,
26+
"Failed to unescape a character due to an invalid escape sequence."
27+
),
28+
}
29+
}
30+
}
31+
32+
#[cfg(feature = "std")]
33+
impl std::error::Error for StringUnescapeError {}
34+
1235
fn unescape_next_fragment(
1336
escaped_string: &str,
1437
) -> Result<(EscapedStringFragment<'_>, &str), StringUnescapeError> {
@@ -55,22 +78,41 @@ fn unescape_next_fragment(
5578
})
5679
}
5780

58-
/// A borrowed escaped string
81+
/// A borrowed escaped string. `EscapedStr` can be used to borrow an escaped string from the input,
82+
/// even when deserialized using `from_str_escaped` or `from_slice_escaped`.
83+
///
84+
/// ```
85+
/// #[derive(serde::Deserialize)]
86+
/// struct Event<'a> {
87+
/// name: heapless::String<16>,
88+
/// #[serde(borrow)]
89+
/// description: serde_json_core::str::EscapedStr<'a>,
90+
/// }
91+
///
92+
/// serde_json_core::de::from_str_escaped::<Event<'_>>(
93+
/// r#"{ "name": "Party\u0021", "description": "I'm throwing a party! Hopefully the \u2600 shines!" }"#,
94+
/// &mut [0; 8],
95+
/// )
96+
/// .unwrap();
97+
/// ```
5998
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
6099
#[serde(rename = "__serde_json_core_escaped_string__")]
61100
pub struct EscapedStr<'a>(pub &'a str);
62101

63102
impl<'a> EscapedStr<'a> {
64103
pub(crate) const NAME: &'static str = "__serde_json_core_escaped_string__";
65104

105+
/// Returns an iterator over the `EscapedStringFragment`s of an escaped string.
66106
pub fn fragments(&self) -> EscapedStringFragmentIter<'a> {
67107
EscapedStringFragmentIter(self.0)
68108
}
69109
}
70110

111+
/// An iterator over the `EscapedStringFragment`s of an escaped string.
71112
pub struct EscapedStringFragmentIter<'a>(&'a str);
72113

73114
impl<'a> EscapedStringFragmentIter<'a> {
115+
/// Views the underlying data as a subslice of the original data.
74116
pub fn as_str(&self) -> EscapedStr<'a> {
75117
EscapedStr(self.0)
76118
}

0 commit comments

Comments
 (0)