macro_rules! unescape_stream_into {
(
$Read:ident: $read:expr,
$Write:ident: |$token:ident| $write:expr,
) => { ... };
}Expand description
Drives an UnescapeStream with runtime-agnostic I/O expressions.
This macro provides an ergonomic, high-level wrapper for processing a stream
of JSON string bytes. It hides the manual boilerplate of looping, calling
try_unescape_next, and handling boundary conditions, and automatically calls
finish() at the end.
§Rationale
The key feature of this macro is that it is runtime-agnostic.
Because the Read and Write arguments are expressions, you can use
this macro identically in both synchronous functions (e.g., with std::io::Read)
and asynchronous functions (e.g., with tokio::io::AsyncRead and .await).
It avoids the “function coloring” problem, where you would otherwise need
separate unescape_sync and unescape_async helper functions.
§Parameters
-
Read: { ... }: An expression that provides the next chunk of byte data. This expression must evaluate to a value compatible withOption<impl AsRef<[u8]>>.Some(bytes)provides the next chunk to the unescaper.Nonesignals the end of the stream (EOF).
-
Write: |$token| { ... }: A closure-like expression that processes anUnescapedToken. The token will be available under the identifier you provide (e.g.,$token).
§Examples
§Synchronous Example (from an iterator)
use json_escape::stream::{unescape_stream_into, UnescapeStream};
use json_escape::token::UnescapedToken;
use std::str;
fn sync_stream() -> Result<(), Box<dyn std::error::Error>> {
// The surrogate pair `\uD83D\uDE00` (😀) is split across parts.
let mut parts = vec![
br#"{"message": "Hello, W\"orld! \uD83D"#.as_slice(),
br#"\uDE00"}"#.as_slice(),
]
.into_iter();
let mut unescaped_string = String::new();
unescape_stream_into! {
Read: {
parts.next() // This is sync
},
Write: |token| {
match token { // This is sync
UnescapedToken::Literal(literal) => {
unescaped_string.push_str(str::from_utf8(literal)?)
}
UnescapedToken::Unescaped(ch) => unescaped_string.push(ch),
}
},
};
assert_eq!(unescaped_string, r#"{"message": "Hello, W"orld! 😀"}"#);
Ok(())
}
sync_stream().unwrap();§Asynchronous Example (from an async stream)
use json_escape::stream::{unescape_stream_into, UnescapeStream};
use json_escape::token::UnescapedToken;
use std::str;
// A simple async iterator for the example
struct AsyncIter<'a> {
iter: <Vec<&'a [u8]> as IntoIterator>::IntoIter,
}
impl<'a> AsyncIter<'a> {
async fn next(&mut self) -> Option<&'a [u8]> {
self.iter.next()
}
}
// A simple async writer for the example
struct AsyncWrite {
write: String,
}
impl AsyncWrite {
async fn write(
&mut self,
token: UnescapedToken<'_>,
) -> Result<(), Box<dyn std::error::Error>> {
match token {
UnescapedToken::Literal(literal) => {
self.write.push_str(str::from_utf8(literal)?)
}
UnescapedToken::Unescaped(ch) => self.write.push(ch),
}
Ok(())
}
}
async fn async_stream() -> Result<(), Box<dyn std::error::Error>> {
let mut parts = AsyncIter {
iter: vec![
br#"{"message": "Hello, W\"orld! \uD83D"#.as_slice(),
br#"\uDE00"}"#.as_slice(),
]
.into_iter(),
};
let mut unescaped_string = AsyncWrite {
write: String::new(),
};
unescape_stream_into! {
Read: {
parts.next().await // This is async
},
Write: |token| {
unescaped_string.write(token).await? // This is async
},
};
assert_eq!(
unescaped_string.write,
r#"{"message": "Hello, W"orld! 😀"}"#
);
Ok(())
}