oxirs_ttl/
async_parser.rs1#[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#[cfg(feature = "async-tokio")]
14#[async_trait::async_trait]
15pub trait AsyncParser: Send + Sync {
16 async fn parse_async<R: AsyncRead + Unpin + Send>(
18 &self,
19 reader: R,
20 ) -> TurtleResult<Vec<Triple>>;
21
22 async fn parse_str_async(&self, content: &str) -> TurtleResult<Vec<Triple>>;
24}
25
26#[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 pub fn new(reader: R) -> Self {
41 Self::with_batch_size(reader, 10_000)
42 }
43
44 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 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 pub fn triples_parsed(&self) -> usize {
73 self.triples_parsed
74 }
75
76 pub fn bytes_read(&self) -> usize {
78 self.bytes_read
79 }
80
81 pub async fn next_batch_async(&mut self) -> TurtleResult<Option<Vec<Triple>>> {
83 use crate::turtle::TurtleParser;
84
85 self.buffer.clear();
87 let mut lines_read = 0;
88 let target_lines = self.batch_size / 10; while lines_read < target_lines {
91 let mut line = String::new();
92 match self.reader.read_line(&mut line).await {
93 Ok(0) => break, 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); }
106
107 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 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 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 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#[cfg(feature = "async-tokio")]
160pub struct AsyncTurtleParser {
161 lenient: bool,
162}
163
164#[cfg(feature = "async-tokio")]
165impl AsyncTurtleParser {
166 pub fn new() -> Self {
168 Self { lenient: false }
169 }
170
171 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 let mut content = String::new();
195 reader
196 .read_to_string(&mut content)
197 .await
198 .map_err(TurtleParseError::io)?;
199
200 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#[cfg(feature = "async-tokio")]
225pub struct AsyncNTriplesParser {
226 lenient: bool,
227}
228
229#[cfg(feature = "async-tokio")]
230impl AsyncNTriplesParser {
231 pub fn new() -> Self {
233 Self { lenient: false }
234 }
235
236 pub fn new_lenient() -> Self {
238 Self { lenient: true }
239 }
240
241 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) => {} 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}