sup_xml/async_io.rs
1//! Async I/O entry points — feature `tokio`.
2//!
3//! The parser itself is synchronous and CPU-bound; async support
4//! here is the standard "slurp bytes via async I/O, then hand to
5//! the existing parser" pattern. It doesn't parse incrementally
6//! across `.await` points — for that, use the streaming reader
7//! ([`crate::Iterparse`] / [`crate::XmlReader`]) on bytes you've
8//! already collected.
9//!
10//! # Example
11//!
12//! ```no_run
13//! # async fn run() -> Result<(), Box<dyn std::error::Error>> {
14//! use sup_xml::async_io::parse_async;
15//!
16//! // Any `tokio::io::AsyncRead` works — `tokio::fs::File` (under
17//! // the `fs` tokio feature), a TCP stream, a `&[u8]` cursor, etc.
18//! let bytes: &[u8] = b"<r><a>hi</a></r>";
19//! let doc = parse_async(bytes).await?;
20//! println!("root: {}", doc.root().name());
21//! # Ok(())
22//! # }
23//! ```
24//!
25//! Bounded-memory usage: callers that don't trust their input
26//! should wrap the reader in `tokio::io::AsyncReadExt::take(MAX)`
27//! before passing.
28
29use tokio::io::{AsyncRead, AsyncReadExt};
30
31use crate::Result;
32use crate::{parse_bytes, ParseOptions};
33use sup_xml_tree::dom::Document;
34
35/// Read `reader` to end asynchronously, then parse the bytes.
36/// Uses default [`ParseOptions`].
37pub async fn parse_async<R: AsyncRead + Unpin>(mut reader: R) -> Result<Document> {
38 let mut bytes = Vec::new();
39 reader.read_to_end(&mut bytes).await.map_err(|e| crate::XmlError::new(
40 crate::ErrorDomain::Io,
41 crate::ErrorLevel::Error,
42 format!("io error reading async source: {e}"),
43 ))?;
44 parse_bytes(&bytes, &ParseOptions::default())
45}
46
47/// Like [`parse_async`] but consults the supplied [`ParseOptions`]
48/// (e.g. `recovery_mode: true`, `external_resolver: Some(...)`).
49pub async fn parse_async_with<R: AsyncRead + Unpin>(
50 mut reader: R, opts: &ParseOptions,
51) -> Result<Document> {
52 let mut bytes = Vec::new();
53 reader.read_to_end(&mut bytes).await.map_err(|e| crate::XmlError::new(
54 crate::ErrorDomain::Io,
55 crate::ErrorLevel::Error,
56 format!("io error reading async source: {e}"),
57 ))?;
58 parse_bytes(&bytes, opts)
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 #[tokio::test(flavor = "current_thread")]
66 async fn parse_from_cursor() {
67 let bytes = b"<r><a>1</a></r>".to_vec();
68 let doc = parse_async(&bytes[..]).await.expect("parse");
69 assert_eq!(doc.root().name(), "r");
70 }
71
72 #[tokio::test(flavor = "current_thread")]
73 async fn malformed_input_errors() {
74 let bytes = b"<r><unclosed>".to_vec();
75 let r = parse_async(&bytes[..]).await;
76 assert!(r.is_err());
77 }
78}