oxttl/
nquads.rs

1//! A [N-Quads](https://www.w3.org/TR/n-quads/) streaming parser implemented by [`NQuadsParser`]
2//! and a serializer implemented by [`NQuadsSerializer`].
3
4use crate::chunker::get_ntriples_file_chunks;
5use crate::line_formats::NQuadsRecognizer;
6#[cfg(feature = "async-tokio")]
7use crate::toolkit::TokioAsyncReaderIterator;
8use crate::toolkit::{Parser, ReaderIterator, SliceIterator, TurtleParseError, TurtleSyntaxError};
9use crate::MIN_PARALLEL_CHUNK_SIZE;
10use oxrdf::{Quad, QuadRef};
11use std::io::{self, Read, Write};
12#[cfg(feature = "async-tokio")]
13use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
14
15/// A [N-Quads](https://www.w3.org/TR/n-quads/) streaming parser.
16///
17/// Support for [N-Quads-star](https://w3c.github.io/rdf-star/cg-spec/2021-12-17.html#n-quads-star) is available behind the `rdf-star` feature and the [`NQuadsParser::with_quoted_triples`] option.
18///
19/// Count the number of people:
20/// ```
21/// use oxrdf::{NamedNodeRef, vocab::rdf};
22/// use oxttl::NQuadsParser;
23///
24/// let file = br#"<http://example.com/foo> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
25/// <http://example.com/foo> <http://schema.org/name> "Foo" .
26/// <http://example.com/bar> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
27/// <http://example.com/bar> <http://schema.org/name> "Bar" ."#;
28///
29/// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
30/// let mut count = 0;
31/// for quad in NQuadsParser::new().for_reader(file.as_ref()) {
32///     let quad = quad?;
33///     if quad.predicate == rdf::TYPE && quad.object == schema_person.into() {
34///         count += 1;
35///     }
36/// }
37/// assert_eq!(2, count);
38/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
39/// ```
40#[derive(Default, Clone)]
41#[must_use]
42pub struct NQuadsParser {
43    unchecked: bool,
44    #[cfg(feature = "rdf-star")]
45    with_quoted_triples: bool,
46}
47
48impl NQuadsParser {
49    /// Builds a new [`NQuadsParser`].
50    #[inline]
51    pub fn new() -> Self {
52        Self::default()
53    }
54
55    /// Assumes the file is valid to make parsing faster.
56    ///
57    /// It will skip some validations.
58    ///
59    /// Note that if the file is actually not valid, broken RDF might be emitted by the parser.
60    #[inline]
61    pub fn unchecked(mut self) -> Self {
62        self.unchecked = true;
63        self
64    }
65
66    /// Enables [N-Quads-star](https://w3c.github.io/rdf-star/cg-spec/2021-12-17.html#n-quads-star).
67    #[cfg(feature = "rdf-star")]
68    #[inline]
69    pub fn with_quoted_triples(mut self) -> Self {
70        self.with_quoted_triples = true;
71        self
72    }
73
74    /// Parses a N-Quads file from a [`Read`] implementation.
75    ///
76    /// Count the number of people:
77    /// ```
78    /// use oxrdf::{NamedNodeRef, vocab::rdf};
79    /// use oxttl::NQuadsParser;
80    ///
81    /// let file = br#"<http://example.com/foo> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
82    /// <http://example.com/foo> <http://schema.org/name> "Foo" .
83    /// <http://example.com/bar> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
84    /// <http://example.com/bar> <http://schema.org/name> "Bar" ."#;
85    ///
86    /// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
87    /// let mut count = 0;
88    /// for quad in NQuadsParser::new().for_reader(file.as_ref()) {
89    ///     let quad = quad?;
90    ///     if quad.predicate == rdf::TYPE && quad.object == schema_person.into() {
91    ///         count += 1;
92    ///     }
93    /// }
94    /// assert_eq!(2, count);
95    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
96    /// ```
97    pub fn for_reader<R: Read>(self, reader: R) -> ReaderNQuadsParser<R> {
98        ReaderNQuadsParser {
99            inner: self.low_level().parser.for_reader(reader),
100        }
101    }
102
103    /// Parses a N-Quads file from a [`AsyncRead`] implementation.
104    ///
105    /// Count the number of people:
106    /// ```
107    /// # #[tokio::main(flavor = "current_thread")]
108    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
109    /// use oxrdf::{NamedNodeRef, vocab::rdf};
110    /// use oxttl::NQuadsParser;
111    ///
112    /// let file = br#"<http://example.com/foo> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
113    /// <http://example.com/foo> <http://schema.org/name> "Foo" .
114    /// <http://example.com/bar> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
115    /// <http://example.com/bar> <http://schema.org/name> "Bar" ."#;
116    ///
117    /// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
118    /// let mut count = 0;
119    /// let mut parser = NQuadsParser::new().for_tokio_async_reader(file.as_ref());
120    /// while let Some(triple) = parser.next().await {
121    ///     let triple = triple?;
122    ///     if triple.predicate == rdf::TYPE && triple.object == schema_person.into() {
123    ///         count += 1;
124    ///     }
125    /// }
126    /// assert_eq!(2, count);
127    /// # Ok(())
128    /// # }
129    /// ```
130    #[cfg(feature = "async-tokio")]
131    pub fn for_tokio_async_reader<R: AsyncRead + Unpin>(
132        self,
133        reader: R,
134    ) -> TokioAsyncReaderNQuadsParser<R> {
135        TokioAsyncReaderNQuadsParser {
136            inner: self.low_level().parser.for_tokio_async_reader(reader),
137        }
138    }
139
140    /// Parses a N-Quads file from a byte slice.
141    ///
142    /// Count the number of people:
143    /// ```
144    /// use oxrdf::{NamedNodeRef, vocab::rdf};
145    /// use oxttl::NQuadsParser;
146    ///
147    /// let file = br#"<http://example.com/foo> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
148    /// <http://example.com/foo> <http://schema.org/name> "Foo" .
149    /// <http://example.com/bar> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
150    /// <http://example.com/bar> <http://schema.org/name> "Bar" ."#;
151    ///
152    /// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
153    /// let mut count = 0;
154    /// for quad in NQuadsParser::new().for_slice(file) {
155    ///     let quad = quad?;
156    ///     if quad.predicate == rdf::TYPE && quad.object == schema_person.into() {
157    ///         count += 1;
158    ///     }
159    /// }
160    /// assert_eq!(2, count);
161    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
162    /// ```
163    pub fn for_slice(self, slice: &[u8]) -> SliceNQuadsParser<'_> {
164        SliceNQuadsParser {
165            inner: NQuadsRecognizer::new_parser(
166                slice,
167                true,
168                true,
169                #[cfg(feature = "rdf-star")]
170                self.with_quoted_triples,
171                self.unchecked,
172            )
173            .into_iter(),
174        }
175    }
176
177    /// Creates a vector of iterators that may be used to parse an NQuads document slice in parallel.
178    /// To dynamically specify target_parallelism, use e.g. [`std::thread::available_parallelism`].
179    /// Intended to work on large documents.
180    ///
181    /// Count the number of people:
182    /// ```
183    /// use oxrdf::vocab::rdf;
184    /// use oxrdf::NamedNodeRef;
185    /// use oxttl::NQuadsParser;
186    /// use rayon::iter::{IntoParallelIterator, ParallelIterator};
187    ///
188    /// let file = br#"<http://example.com/foo> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
189    /// <http://example.com/foo> <http://schema.org/name> "Foo" .
190    /// <http://example.com/bar> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
191    /// <http://example.com/bar> <http://schema.org/name> "Bar" ."#;
192    ///
193    /// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
194    /// let readers = NQuadsParser::new().split_slice_for_parallel_parsing(file.as_ref(), 2);
195    /// let count = readers
196    ///     .into_par_iter()
197    ///     .map(|reader| {
198    ///         let mut count = 0;
199    ///         for quad in reader {
200    ///             let quad = quad.unwrap();
201    ///             if quad.predicate == rdf::TYPE && quad.object == schema_person.into() {
202    ///                 count += 1;
203    ///             }
204    ///         }
205    ///         count
206    ///     })
207    ///     .sum();
208    /// assert_eq!(2, count);
209    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
210    /// ```
211    pub fn split_slice_for_parallel_parsing<'a>(
212        &self,
213        slice: &'a [u8],
214        target_parallelism: usize,
215    ) -> Vec<SliceNQuadsParser<'a>> {
216        let n_chunks = (slice.len() / MIN_PARALLEL_CHUNK_SIZE).clamp(1, target_parallelism);
217        get_ntriples_file_chunks(slice, n_chunks)
218            .into_iter()
219            .map(|(start, end)| self.clone().for_slice(&slice[start..end]))
220            .collect()
221    }
222
223    /// Allows to parse a N-Quads file by using a low-level API.
224    ///
225    /// Count the number of people:
226    /// ```
227    /// use oxrdf::{NamedNodeRef, vocab::rdf};
228    /// use oxttl::NQuadsParser;
229    ///
230    /// let file: [&[u8]; 4] = [
231    ///     b"<http://example.com/foo> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .\n",
232    ///     b"<http://example.com/foo> <http://schema.org/name> \"Foo\" .\n",
233    ///     b"<http://example.com/bar> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .\n",
234    ///     b"<http://example.com/bar> <http://schema.org/name> \"Bar\" .\n"
235    /// ];
236    ///
237    /// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
238    /// let mut count = 0;
239    /// let mut parser = NQuadsParser::new().low_level();
240    /// let mut file_chunks = file.iter();
241    /// while !parser.is_end() {
242    ///     // We feed more data to the parser
243    ///     if let Some(chunk) = file_chunks.next() {
244    ///         parser.extend_from_slice(chunk);    
245    ///     } else {
246    ///         parser.end(); // It's finished
247    ///     }
248    ///     // We read as many quads from the parser as possible
249    ///     while let Some(quad) = parser.parse_next() {
250    ///         let quad = quad?;
251    ///         if quad.predicate == rdf::TYPE && quad.object == schema_person.into() {
252    ///             count += 1;
253    ///         }
254    ///     }
255    /// }
256    /// assert_eq!(2, count);
257    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
258    /// ```
259    #[allow(clippy::unused_self)]
260    pub fn low_level(self) -> LowLevelNQuadsParser {
261        LowLevelNQuadsParser {
262            parser: NQuadsRecognizer::new_parser(
263                Vec::new(),
264                false,
265                true,
266                #[cfg(feature = "rdf-star")]
267                self.with_quoted_triples,
268                self.unchecked,
269            ),
270        }
271    }
272}
273
274/// Parses a N-Quads file from a [`Read`] implementation.
275///
276/// Can be built using [`NQuadsParser::for_reader`].
277///
278/// Count the number of people:
279/// ```
280/// use oxrdf::{NamedNodeRef, vocab::rdf};
281/// use oxttl::NQuadsParser;
282///
283/// let file = br#"<http://example.com/foo> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
284/// <http://example.com/foo> <http://schema.org/name> "Foo" .
285/// <http://example.com/bar> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
286/// <http://example.com/bar> <http://schema.org/name> "Bar" ."#;
287///
288/// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
289/// let mut count = 0;
290/// for quad in NQuadsParser::new().for_reader(file.as_ref()) {
291///     let quad = quad?;
292///     if quad.predicate == rdf::TYPE && quad.object == schema_person.into() {
293///         count += 1;
294///     }
295/// }
296/// assert_eq!(2, count);
297/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
298/// ```
299#[must_use]
300pub struct ReaderNQuadsParser<R: Read> {
301    inner: ReaderIterator<R, NQuadsRecognizer>,
302}
303
304impl<R: Read> Iterator for ReaderNQuadsParser<R> {
305    type Item = Result<Quad, TurtleParseError>;
306
307    fn next(&mut self) -> Option<Self::Item> {
308        self.inner.next()
309    }
310}
311
312/// Parses a N-Quads file from a [`AsyncRead`] implementation.
313///
314/// Can be built using [`NQuadsParser::for_tokio_async_reader`].
315///
316/// Count the number of people:
317/// ```
318/// # #[tokio::main(flavor = "current_thread")]
319/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
320/// use oxrdf::{NamedNodeRef, vocab::rdf};
321/// use oxttl::NQuadsParser;
322///
323/// let file = br#"<http://example.com/foo> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
324/// <http://example.com/foo> <http://schema.org/name> "Foo" .
325/// <http://example.com/bar> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
326/// <http://example.com/bar> <http://schema.org/name> "Bar" ."#;
327///
328/// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
329/// let mut count = 0;
330/// let mut parser = NQuadsParser::new().for_tokio_async_reader(file.as_ref());
331/// while let Some(triple) = parser.next().await {
332///     let triple = triple?;
333///     if triple.predicate == rdf::TYPE && triple.object == schema_person.into() {
334///         count += 1;
335///     }
336/// }
337/// assert_eq!(2, count);
338/// # Ok(())
339/// # }
340/// ```
341#[cfg(feature = "async-tokio")]
342#[must_use]
343pub struct TokioAsyncReaderNQuadsParser<R: AsyncRead + Unpin> {
344    inner: TokioAsyncReaderIterator<R, NQuadsRecognizer>,
345}
346
347#[cfg(feature = "async-tokio")]
348impl<R: AsyncRead + Unpin> TokioAsyncReaderNQuadsParser<R> {
349    /// Reads the next triple or returns `None` if the file is finished.
350    pub async fn next(&mut self) -> Option<Result<Quad, TurtleParseError>> {
351        self.inner.next().await
352    }
353}
354
355/// Parses a N-Quads file from a byte slice.
356///
357/// Can be built using [`NQuadsParser::for_slice`].
358///
359/// Count the number of people:
360/// ```
361/// use oxrdf::{NamedNodeRef, vocab::rdf};
362/// use oxttl::NQuadsParser;
363///
364/// let file = br#"<http://example.com/foo> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
365/// <http://example.com/foo> <http://schema.org/name> "Foo" .
366/// <http://example.com/bar> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
367/// <http://example.com/bar> <http://schema.org/name> "Bar" ."#;
368///
369/// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
370/// let mut count = 0;
371/// for quad in NQuadsParser::new().for_slice(file) {
372///     let quad = quad?;
373///     if quad.predicate == rdf::TYPE && quad.object == schema_person.into() {
374///         count += 1;
375///     }
376/// }
377/// assert_eq!(2, count);
378/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
379/// ```
380#[must_use]
381pub struct SliceNQuadsParser<'a> {
382    inner: SliceIterator<'a, NQuadsRecognizer>,
383}
384
385impl Iterator for SliceNQuadsParser<'_> {
386    type Item = Result<Quad, TurtleSyntaxError>;
387
388    fn next(&mut self) -> Option<Self::Item> {
389        self.inner.next()
390    }
391}
392
393/// Parses a N-Quads file by using a low-level API.
394///
395/// Can be built using [`NQuadsParser::low_level`].
396///
397/// Count the number of people:
398/// ```
399/// use oxrdf::{NamedNodeRef, vocab::rdf};
400/// use oxttl::NQuadsParser;
401///
402/// let file: [&[u8]; 4] = [
403///     b"<http://example.com/foo> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .\n",
404///     b"<http://example.com/foo> <http://schema.org/name> \"Foo\" .\n",
405///     b"<http://example.com/bar> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .\n",
406///     b"<http://example.com/bar> <http://schema.org/name> \"Bar\" .\n"
407/// ];
408///
409/// let schema_person = NamedNodeRef::new("http://schema.org/Person")?;
410/// let mut count = 0;
411/// let mut parser = NQuadsParser::new().low_level();
412/// let mut file_chunks = file.iter();
413/// while !parser.is_end() {
414///     // We feed more data to the parser
415///     if let Some(chunk) = file_chunks.next() {
416///         parser.extend_from_slice(chunk);    
417///     } else {
418///         parser.end(); // It's finished
419///     }
420///     // We read as many quads from the parser as possible
421///     while let Some(quad) = parser.parse_next() {
422///         let quad = quad?;
423///         if quad.predicate == rdf::TYPE && quad.object == schema_person.into() {
424///             count += 1;
425///         }
426///     }
427/// }
428/// assert_eq!(2, count);
429/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
430/// ```
431pub struct LowLevelNQuadsParser {
432    parser: Parser<Vec<u8>, NQuadsRecognizer>,
433}
434
435impl LowLevelNQuadsParser {
436    /// Adds some extra bytes to the parser. Should be called when [`parse_next`](Self::parse_next) returns [`None`] and there is still unread data.
437    pub fn extend_from_slice(&mut self, other: &[u8]) {
438        self.parser.extend_from_slice(other)
439    }
440
441    /// Tell the parser that the file is finished.
442    ///
443    /// This triggers the parsing of the final bytes and might lead [`parse_next`](Self::parse_next) to return some extra values.
444    pub fn end(&mut self) {
445        self.parser.end()
446    }
447
448    /// Returns if the parsing is finished i.e. [`end`](Self::end) has been called and [`parse_next`](Self::parse_next) is always going to return `None`.
449    pub fn is_end(&self) -> bool {
450        self.parser.is_end()
451    }
452
453    /// Attempt to parse a new quad from the already provided data.
454    ///
455    /// Returns [`None`] if the parsing is finished or more data is required.
456    /// If it is the case more data should be fed using [`extend_from_slice`](Self::extend_from_slice).
457    pub fn parse_next(&mut self) -> Option<Result<Quad, TurtleSyntaxError>> {
458        self.parser.parse_next()
459    }
460}
461
462/// A [N-Quads](https://www.w3.org/TR/n-quads/) serializer.
463///
464/// Support for [N-Quads-star](https://w3c.github.io/rdf-star/cg-spec/2021-12-17.html#n-quads-star) is available behind the `rdf-star` feature.
465///
466/// ```
467/// use oxrdf::{NamedNodeRef, QuadRef};
468/// use oxrdf::vocab::rdf;
469/// use oxttl::NQuadsSerializer;
470///
471/// let mut serializer = NQuadsSerializer::new().for_writer(Vec::new());
472/// serializer.serialize_quad(QuadRef::new(
473///     NamedNodeRef::new("http://example.com#me")?,
474///     rdf::TYPE,
475///     NamedNodeRef::new("http://schema.org/Person")?,
476///     NamedNodeRef::new("http://example.com")?,
477/// ))?;
478/// assert_eq!(
479///     b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> <http://example.com> .\n",
480///     serializer.finish().as_slice()
481/// );
482/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
483/// ```
484#[derive(Default, Clone)]
485#[must_use]
486#[allow(clippy::empty_structs_with_brackets)]
487pub struct NQuadsSerializer {}
488
489impl NQuadsSerializer {
490    /// Builds a new [`NQuadsSerializer`].
491    #[inline]
492    pub fn new() -> Self {
493        Self {}
494    }
495
496    /// Writes a N-Quads file to a [`Write`] implementation.
497    ///
498    /// ```
499    /// use oxrdf::{NamedNodeRef, QuadRef};
500    /// use oxrdf::vocab::rdf;
501    /// use oxttl::NQuadsSerializer;
502    ///
503    /// let mut serializer = NQuadsSerializer::new().for_writer(Vec::new());
504    /// serializer.serialize_quad(QuadRef::new(
505    ///     NamedNodeRef::new("http://example.com#me")?,
506    ///     rdf::TYPE,
507    ///     NamedNodeRef::new("http://schema.org/Person")?,
508    ///     NamedNodeRef::new("http://example.com")?,
509    /// ))?;
510    /// assert_eq!(
511    ///     b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> <http://example.com> .\n",
512    ///     serializer.finish().as_slice()
513    /// );
514    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
515    /// ```
516    pub fn for_writer<W: Write>(self, writer: W) -> WriterNQuadsSerializer<W> {
517        WriterNQuadsSerializer {
518            writer,
519            low_level_writer: self.low_level(),
520        }
521    }
522
523    /// Writes a N-Quads file to a [`AsyncWrite`] implementation.
524    ///
525    /// ```
526    /// # #[tokio::main(flavor = "current_thread")]
527    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
528    /// use oxrdf::{NamedNodeRef, QuadRef};
529    /// use oxttl::NQuadsSerializer;
530    /// use oxrdf::vocab::rdf;
531    ///
532    /// let mut serializer = NQuadsSerializer::new().for_tokio_async_writer(Vec::new());
533    /// serializer.serialize_quad(QuadRef::new(
534    ///     NamedNodeRef::new("http://example.com#me")?,
535    ///     rdf::TYPE,
536    ///     NamedNodeRef::new("http://schema.org/Person")?,
537    ///     NamedNodeRef::new("http://example.com")?,
538    /// )).await?;
539    /// assert_eq!(
540    ///     b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> <http://example.com> .\n",
541    ///     serializer.finish().as_slice()
542    /// );
543    /// # Ok(())
544    /// # }
545    /// ```
546    #[cfg(feature = "async-tokio")]
547    pub fn for_tokio_async_writer<W: AsyncWrite + Unpin>(
548        self,
549        writer: W,
550    ) -> TokioAsyncWriterNQuadsSerializer<W> {
551        TokioAsyncWriterNQuadsSerializer {
552            writer,
553            low_level_writer: self.low_level(),
554            buffer: Vec::new(),
555        }
556    }
557
558    /// Builds a low-level N-Quads writer.
559    ///
560    /// ```
561    /// use oxrdf::{NamedNodeRef, QuadRef};
562    /// use oxrdf::vocab::rdf;
563    /// use oxttl::NQuadsSerializer;
564    ///
565    /// let mut buf = Vec::new();
566    /// let mut serializer = NQuadsSerializer::new().low_level();
567    /// serializer.serialize_quad(QuadRef::new(
568    ///     NamedNodeRef::new("http://example.com#me")?,
569    ///     rdf::TYPE,
570    ///     NamedNodeRef::new("http://schema.org/Person")?,
571    ///     NamedNodeRef::new("http://example.com")?,
572    /// ), &mut buf)?;
573    /// assert_eq!(
574    ///     b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> <http://example.com> .\n",
575    ///     buf.as_slice()
576    /// );
577    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
578    /// ```
579    #[allow(clippy::unused_self)]
580    pub fn low_level(self) -> LowLevelNQuadsSerializer {
581        LowLevelNQuadsSerializer {}
582    }
583}
584
585/// Writes a N-Quads file to a [`Write`] implementation.
586///
587/// Can be built using [`NQuadsSerializer::for_writer`].
588///
589/// ```
590/// use oxrdf::{NamedNodeRef, QuadRef};
591/// use oxrdf::vocab::rdf;
592/// use oxttl::NQuadsSerializer;
593///
594/// let mut serializer = NQuadsSerializer::new().for_writer(Vec::new());
595/// serializer.serialize_quad(QuadRef::new(
596///     NamedNodeRef::new("http://example.com#me")?,
597///     rdf::TYPE,
598///     NamedNodeRef::new("http://schema.org/Person")?,
599///     NamedNodeRef::new("http://example.com")?,
600/// ))?;
601/// assert_eq!(
602///     b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> <http://example.com> .\n",
603///     serializer.finish().as_slice()
604/// );
605/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
606/// ```
607#[must_use]
608pub struct WriterNQuadsSerializer<W: Write> {
609    writer: W,
610    low_level_writer: LowLevelNQuadsSerializer,
611}
612
613impl<W: Write> WriterNQuadsSerializer<W> {
614    /// Writes an extra quad.
615    pub fn serialize_quad<'a>(&mut self, q: impl Into<QuadRef<'a>>) -> io::Result<()> {
616        self.low_level_writer.serialize_quad(q, &mut self.writer)
617    }
618
619    /// Ends the write process and returns the underlying [`Write`].
620    pub fn finish(self) -> W {
621        self.writer
622    }
623}
624
625/// Writes a N-Quads file to a [`AsyncWrite`] implementation.
626///
627/// Can be built using [`NQuadsSerializer::for_tokio_async_writer`].
628///
629/// ```
630/// # #[tokio::main(flavor = "current_thread")]
631/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
632/// use oxrdf::{NamedNodeRef, QuadRef};
633/// use oxrdf::vocab::rdf;
634/// use oxttl::NQuadsSerializer;
635///
636/// let mut serializer = NQuadsSerializer::new().for_tokio_async_writer(Vec::new());
637/// serializer.serialize_quad(QuadRef::new(
638///     NamedNodeRef::new("http://example.com#me")?,
639///     rdf::TYPE,
640///     NamedNodeRef::new("http://schema.org/Person")?,
641///     NamedNodeRef::new("http://example.com")?,
642/// )).await?;
643/// assert_eq!(
644///     b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> <http://example.com> .\n",
645///     serializer.finish().as_slice()
646/// );
647/// # Ok(())
648/// # }
649/// ```
650#[cfg(feature = "async-tokio")]
651#[must_use]
652pub struct TokioAsyncWriterNQuadsSerializer<W: AsyncWrite + Unpin> {
653    writer: W,
654    low_level_writer: LowLevelNQuadsSerializer,
655    buffer: Vec<u8>,
656}
657
658#[cfg(feature = "async-tokio")]
659impl<W: AsyncWrite + Unpin> TokioAsyncWriterNQuadsSerializer<W> {
660    /// Writes an extra quad.
661    pub async fn serialize_quad<'a>(&mut self, q: impl Into<QuadRef<'a>>) -> io::Result<()> {
662        self.low_level_writer.serialize_quad(q, &mut self.buffer)?;
663        self.writer.write_all(&self.buffer).await?;
664        self.buffer.clear();
665        Ok(())
666    }
667
668    /// Ends the write process and returns the underlying [`Write`].
669    pub fn finish(self) -> W {
670        self.writer
671    }
672}
673
674/// Writes a N-Quads file by using a low-level API.
675///
676/// Can be built using [`NQuadsSerializer::low_level`].
677///
678/// ```
679/// use oxrdf::{NamedNodeRef, QuadRef};
680/// use oxrdf::vocab::rdf;
681/// use oxttl::NQuadsSerializer;
682///
683/// let mut buf = Vec::new();
684/// let mut serializer = NQuadsSerializer::new().low_level();
685/// serializer.serialize_quad(QuadRef::new(
686///     NamedNodeRef::new("http://example.com#me")?,
687///     rdf::TYPE,
688///     NamedNodeRef::new("http://schema.org/Person")?,
689///     NamedNodeRef::new("http://example.com")?,
690/// ), &mut buf)?;
691/// assert_eq!(
692///     b"<http://example.com#me> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> <http://example.com> .\n",
693///     buf.as_slice()
694/// );
695/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
696/// ```
697#[allow(clippy::empty_structs_with_brackets)]
698pub struct LowLevelNQuadsSerializer {}
699
700impl LowLevelNQuadsSerializer {
701    /// Writes an extra quad.
702    #[allow(clippy::unused_self)]
703    pub fn serialize_quad<'a>(
704        &mut self,
705        q: impl Into<QuadRef<'a>>,
706        mut writer: impl Write,
707    ) -> io::Result<()> {
708        writeln!(writer, "{} .", q.into())
709    }
710}