Skip to main content

oxirs_ttl/
async_parser.rs

1//! Async I/O support for RDF parsing using Tokio
2//!
3//! This module provides async parsing capabilities for non-blocking I/O operations.
4
5#[cfg(feature = "async-tokio")]
6use tokio::io::{AsyncBufRead, AsyncBufReadExt, AsyncRead, AsyncReadExt, BufReader};
7
8use crate::error::{TurtleParseError, TurtleResult};
9use crate::toolkit::Parser;
10use oxirs_core::model::Triple;
11
12/// Async parser trait for non-blocking RDF parsing
13#[cfg(feature = "async-tokio")]
14#[async_trait::async_trait]
15pub trait AsyncParser: Send + Sync {
16    /// Parse a document from an async reader
17    async fn parse_async<R: AsyncRead + Unpin + Send>(
18        &self,
19        reader: R,
20    ) -> TurtleResult<Vec<Triple>>;
21
22    /// Parse a document from a string
23    async fn parse_str_async(&self, content: &str) -> TurtleResult<Vec<Triple>>;
24}
25
26/// Async streaming parser for processing large files
27#[cfg(feature = "async-tokio")]
28pub struct AsyncStreamingParser<R: AsyncBufRead + Unpin> {
29    reader: R,
30    buffer: String,
31    prefix_declarations: String,
32    triples_parsed: usize,
33    bytes_read: usize,
34    batch_size: usize,
35}
36
37#[cfg(feature = "async-tokio")]
38impl<R: AsyncRead + Unpin + Send> AsyncStreamingParser<BufReader<R>> {
39    /// Create a new async streaming parser
40    pub fn new(reader: R) -> Self {
41        Self::with_batch_size(reader, 10_000)
42    }
43
44    /// Create a streaming parser with a specific batch size
45    pub fn with_batch_size(reader: R, batch_size: usize) -> Self {
46        Self {
47            reader: BufReader::new(reader),
48            buffer: String::new(),
49            prefix_declarations: String::new(),
50            triples_parsed: 0,
51            bytes_read: 0,
52            batch_size,
53        }
54    }
55}
56
57#[cfg(feature = "async-tokio")]
58impl<R: AsyncBufRead + Unpin> AsyncStreamingParser<R> {
59    /// Create from an existing async BufRead
60    pub fn from_buf_reader(reader: R, batch_size: usize) -> Self {
61        Self {
62            reader,
63            buffer: String::new(),
64            prefix_declarations: String::new(),
65            triples_parsed: 0,
66            bytes_read: 0,
67            batch_size,
68        }
69    }
70
71    /// Get the number of triples parsed so far
72    pub fn triples_parsed(&self) -> usize {
73        self.triples_parsed
74    }
75
76    /// Get the number of bytes read so far
77    pub fn bytes_read(&self) -> usize {
78        self.bytes_read
79    }
80
81    /// Parse the next batch of triples asynchronously
82    pub async fn next_batch_async(&mut self) -> TurtleResult<Option<Vec<Triple>>> {
83        use crate::turtle::TurtleParser;
84
85        // Read up to batch_size lines
86        self.buffer.clear();
87        let mut lines_read = 0;
88        let target_lines = self.batch_size / 10; // Rough estimate: ~10 triples per line
89
90        while lines_read < target_lines {
91            let mut line = String::new();
92            match self.reader.read_line(&mut line).await {
93                Ok(0) => break, // EOF
94                Ok(n) => {
95                    self.bytes_read += n;
96                    self.buffer.push_str(&line);
97                    lines_read += 1;
98                }
99                Err(e) => return Err(TurtleParseError::io(e)),
100            }
101        }
102
103        if self.buffer.is_empty() {
104            return Ok(None); // EOF
105        }
106
107        // Extract prefix declarations
108        for line in self.buffer.lines() {
109            let trimmed = line.trim();
110            if (trimmed.starts_with("@prefix") || trimmed.starts_with("@base"))
111                && !self.prefix_declarations.contains(trimmed)
112            {
113                self.prefix_declarations.push_str(trimmed);
114                self.prefix_declarations.push('\n');
115            }
116        }
117
118        // Parse the complete document (prefixes + current batch)
119        let document = format!("{}{}", self.prefix_declarations, self.buffer);
120
121        let parser = TurtleParser::new();
122        match parser.parse_document(&document) {
123            Ok(triples) => {
124                self.triples_parsed += triples.len();
125                Ok(Some(triples))
126            }
127            Err(e) => Err(e),
128        }
129    }
130
131    /// Stream all triples with a callback
132    pub async fn stream_with_callback<F>(&mut self, mut callback: F) -> TurtleResult<usize>
133    where
134        F: FnMut(Vec<Triple>),
135    {
136        let mut total = 0;
137
138        while let Some(batch) = self.next_batch_async().await? {
139            total += batch.len();
140            callback(batch);
141        }
142
143        Ok(total)
144    }
145
146    /// Collect all triples into a vector
147    pub async fn collect_all_async(&mut self) -> TurtleResult<Vec<Triple>> {
148        let mut all_triples = Vec::new();
149
150        while let Some(batch) = self.next_batch_async().await? {
151            all_triples.extend(batch);
152        }
153
154        Ok(all_triples)
155    }
156}
157
158/// Async Turtle parser implementation
159#[cfg(feature = "async-tokio")]
160pub struct AsyncTurtleParser {
161    lenient: bool,
162}
163
164#[cfg(feature = "async-tokio")]
165impl AsyncTurtleParser {
166    /// Create a new async Turtle parser
167    pub fn new() -> Self {
168        Self { lenient: false }
169    }
170
171    /// Create a new lenient async Turtle parser
172    pub fn new_lenient() -> Self {
173        Self { lenient: true }
174    }
175}
176
177#[cfg(feature = "async-tokio")]
178impl Default for AsyncTurtleParser {
179    fn default() -> Self {
180        Self::new()
181    }
182}
183
184#[cfg(feature = "async-tokio")]
185#[async_trait::async_trait]
186impl AsyncParser for AsyncTurtleParser {
187    async fn parse_async<R: AsyncRead + Unpin + Send>(
188        &self,
189        mut reader: R,
190    ) -> TurtleResult<Vec<Triple>> {
191        use crate::turtle::TurtleParser;
192
193        // Read entire content
194        let mut content = String::new();
195        reader
196            .read_to_string(&mut content)
197            .await
198            .map_err(TurtleParseError::io)?;
199
200        // Parse using sync parser
201        let parser = if self.lenient {
202            TurtleParser::new_lenient()
203        } else {
204            TurtleParser::new()
205        };
206
207        parser.parse_document(&content)
208    }
209
210    async fn parse_str_async(&self, content: &str) -> TurtleResult<Vec<Triple>> {
211        use crate::turtle::TurtleParser;
212
213        let parser = if self.lenient {
214            TurtleParser::new_lenient()
215        } else {
216            TurtleParser::new()
217        };
218
219        parser.parse_document(content)
220    }
221}
222
223/// Async N-Triples parser implementation
224#[cfg(feature = "async-tokio")]
225pub struct AsyncNTriplesParser {
226    lenient: bool,
227}
228
229#[cfg(feature = "async-tokio")]
230impl AsyncNTriplesParser {
231    /// Create a new async N-Triples parser
232    pub fn new() -> Self {
233        Self { lenient: false }
234    }
235
236    /// Create a new lenient async N-Triples parser
237    pub fn new_lenient() -> Self {
238        Self { lenient: true }
239    }
240
241    /// Parse lines from an async reader
242    pub async fn parse_lines<R: AsyncBufRead + Unpin>(
243        &self,
244        reader: R,
245    ) -> TurtleResult<Vec<Triple>> {
246        use crate::ntriples::NTriplesParser;
247
248        let mut lines = reader.lines();
249        let mut all_triples = Vec::new();
250        let parser = NTriplesParser::new();
251        let mut line_number = 0;
252
253        while let Some(line) = lines.next_line().await.map_err(TurtleParseError::io)? {
254            line_number += 1;
255
256            match parser.parse_line(&line, line_number) {
257                Ok(Some(triple)) => all_triples.push(triple),
258                Ok(None) => {} // Empty line or comment
259                Err(e) if self.lenient => {
260                    eprintln!("Warning: Parse error on line {}: {}", line_number, e);
261                }
262                Err(e) => return Err(e),
263            }
264        }
265
266        Ok(all_triples)
267    }
268}
269
270#[cfg(feature = "async-tokio")]
271impl Default for AsyncNTriplesParser {
272    fn default() -> Self {
273        Self::new()
274    }
275}
276
277#[cfg(feature = "async-tokio")]
278#[async_trait::async_trait]
279impl AsyncParser for AsyncNTriplesParser {
280    async fn parse_async<R: AsyncRead + Unpin + Send>(
281        &self,
282        reader: R,
283    ) -> TurtleResult<Vec<Triple>> {
284        self.parse_lines(BufReader::new(reader)).await
285    }
286
287    async fn parse_str_async(&self, content: &str) -> TurtleResult<Vec<Triple>> {
288        use crate::ntriples::NTriplesParser;
289        use std::io::Cursor;
290
291        let parser = NTriplesParser::new();
292        let content_owned = content.to_string();
293        let result: Result<Vec<_>, _> = parser.for_reader(Cursor::new(content_owned)).collect();
294        result
295    }
296}
297
298#[cfg(not(feature = "async-tokio"))]
299compile_error!("Async parsing requires the 'async-tokio' feature to be enabled");
300
301#[cfg(all(test, feature = "async-tokio"))]
302mod tests {
303    use super::*;
304
305    #[tokio::test]
306    async fn test_async_turtle_parser() {
307        let turtle = r#"
308            @prefix ex: <http://example.org/> .
309            ex:alice ex:name "Alice" .
310            ex:bob ex:name "Bob" .
311        "#;
312
313        let parser = AsyncTurtleParser::new();
314        let result = parser.parse_str_async(turtle).await;
315
316        assert!(result.is_ok());
317        let triples = result.expect("result should be Ok");
318        assert_eq!(triples.len(), 2);
319    }
320
321    #[tokio::test]
322    async fn test_async_ntriples_parser() {
323        let nt = "<http://example.org/s> <http://example.org/p> \"o\" .\n\
324                  <http://example.org/s2> <http://example.org/p2> \"o2\" .";
325
326        let parser = AsyncNTriplesParser::new();
327        let result = parser.parse_str_async(nt).await;
328
329        assert!(result.is_ok());
330        let triples = result.expect("result should be Ok");
331        assert_eq!(triples.len(), 2);
332    }
333
334    #[tokio::test]
335    async fn test_async_streaming_parser() {
336        let turtle = r#"
337            @prefix ex: <http://example.org/> .
338            ex:alice ex:name "Alice" .
339            ex:bob ex:name "Bob" .
340            ex:charlie ex:name "Charlie" .
341        "#;
342
343        let cursor = std::io::Cursor::new(turtle);
344        let async_reader = tokio::io::BufReader::new(cursor);
345        let mut parser = AsyncStreamingParser::from_buf_reader(async_reader, 10);
346
347        let triples = parser.collect_all_async().await;
348
349        assert!(triples.is_ok());
350        assert_eq!(triples.expect("operation should succeed").len(), 3);
351    }
352
353    #[tokio::test]
354    async fn test_async_streaming_with_callback() {
355        let turtle = r#"
356            @prefix ex: <http://example.org/> .
357            ex:alice ex:name "Alice" .
358            ex:bob ex:name "Bob" .
359        "#;
360
361        let cursor = std::io::Cursor::new(turtle);
362        let async_reader = tokio::io::BufReader::new(cursor);
363        let mut parser = AsyncStreamingParser::from_buf_reader(async_reader, 10);
364
365        let mut count = 0;
366        let result = parser
367            .stream_with_callback(|batch| {
368                count += batch.len();
369            })
370            .await;
371
372        assert!(result.is_ok());
373        assert_eq!(count, 2);
374    }
375}