unescape_stream_into

Macro unescape_stream_into 

Source
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 with Option<impl AsRef<[u8]>>.

    • Some(bytes) provides the next chunk to the unescaper.
    • None signals the end of the stream (EOF).
  • Write: |$token| { ... }: A closure-like expression that processes an UnescapedToken. 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(())
}