oxrdfio/
serializer.rs

1//! Utilities to write RDF graphs and datasets.
2
3use crate::format::RdfFormat;
4#[cfg(feature = "async-tokio")]
5use oxjsonld::TokioAsyncWriterJsonLdSerializer;
6use oxjsonld::{JsonLdProfile, JsonLdSerializer, WriterJsonLdSerializer};
7use oxrdf::{GraphNameRef, IriParseError, QuadRef, TripleRef};
8#[cfg(feature = "async-tokio")]
9use oxrdfxml::TokioAsyncWriterRdfXmlSerializer;
10use oxrdfxml::{RdfXmlSerializer, WriterRdfXmlSerializer};
11#[cfg(feature = "async-tokio")]
12use oxttl::nquads::TokioAsyncWriterNQuadsSerializer;
13use oxttl::nquads::{NQuadsSerializer, WriterNQuadsSerializer};
14#[cfg(feature = "async-tokio")]
15use oxttl::ntriples::TokioAsyncWriterNTriplesSerializer;
16use oxttl::ntriples::{NTriplesSerializer, WriterNTriplesSerializer};
17#[cfg(feature = "async-tokio")]
18use oxttl::trig::TokioAsyncWriterTriGSerializer;
19use oxttl::trig::{TriGSerializer, WriterTriGSerializer};
20#[cfg(feature = "async-tokio")]
21use oxttl::turtle::TokioAsyncWriterTurtleSerializer;
22use oxttl::turtle::{TurtleSerializer, WriterTurtleSerializer};
23use std::io::{self, Write};
24#[cfg(feature = "async-tokio")]
25use tokio::io::AsyncWrite;
26
27/// A serializer for RDF serialization formats.
28///
29/// It currently supports the following formats:
30/// * [JSON-LD](https://www.w3.org/TR/json-ld/) ([`RdfFormat::JsonLd`])
31/// * [N3](https://w3c.github.io/N3/spec/) ([`RdfFormat::N3`])
32/// * [N-Quads](https://www.w3.org/TR/n-quads/) ([`RdfFormat::NQuads`])
33/// * [canonical](https://www.w3.org/TR/n-triples/#canonical-ntriples) [N-Triples](https://www.w3.org/TR/n-triples/) ([`RdfFormat::NTriples`])
34/// * [RDF/XML](https://www.w3.org/TR/rdf-syntax-grammar/) ([`RdfFormat::RdfXml`])
35/// * [TriG](https://www.w3.org/TR/trig/) ([`RdfFormat::TriG`])
36/// * [Turtle](https://www.w3.org/TR/turtle/) ([`RdfFormat::Turtle`])
37///
38/// ```
39/// use oxrdfio::{RdfFormat, RdfSerializer};
40/// use oxrdf::{Quad, NamedNode};
41///
42/// let mut serializer = RdfSerializer::from_format(RdfFormat::NQuads).for_writer(Vec::new());
43/// serializer.serialize_quad(&Quad {
44///    subject: NamedNode::new("http://example.com/s")?.into(),
45///    predicate: NamedNode::new("http://example.com/p")?,
46///    object: NamedNode::new("http://example.com/o")?.into(),
47///    graph_name: NamedNode::new("http://example.com/g")?.into()
48/// })?;
49/// assert_eq!(serializer.finish()?, b"<http://example.com/s> <http://example.com/p> <http://example.com/o> <http://example.com/g> .\n");
50/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
51/// ```
52#[must_use]
53#[derive(Clone)]
54pub struct RdfSerializer {
55    inner: RdfSerializerKind,
56}
57
58#[derive(Clone)]
59enum RdfSerializerKind {
60    JsonLd(JsonLdSerializer),
61    NQuads(NQuadsSerializer),
62    NTriples(NTriplesSerializer),
63    RdfXml(RdfXmlSerializer),
64    TriG(TriGSerializer),
65    Turtle(TurtleSerializer),
66}
67
68impl RdfSerializer {
69    /// Builds a serializer for the given format
70    #[inline]
71    pub fn from_format(format: RdfFormat) -> Self {
72        Self {
73            inner: match format {
74                RdfFormat::JsonLd { .. } => RdfSerializerKind::JsonLd(JsonLdSerializer::new()),
75                RdfFormat::NQuads => RdfSerializerKind::NQuads(NQuadsSerializer::new()),
76                RdfFormat::NTriples => RdfSerializerKind::NTriples(NTriplesSerializer::new()),
77                RdfFormat::RdfXml => RdfSerializerKind::RdfXml(RdfXmlSerializer::new()),
78                RdfFormat::TriG => RdfSerializerKind::TriG(TriGSerializer::new()),
79                RdfFormat::Turtle | RdfFormat::N3 => {
80                    RdfSerializerKind::Turtle(TurtleSerializer::new())
81                }
82            },
83        }
84    }
85
86    /// The format the serializer serializes to.
87    ///
88    /// ```
89    /// use oxrdfio::{RdfFormat, RdfSerializer};
90    ///
91    /// assert_eq!(
92    ///     RdfSerializer::from_format(RdfFormat::Turtle).format(),
93    ///     RdfFormat::Turtle
94    /// );
95    /// ```
96    pub fn format(&self) -> RdfFormat {
97        match &self.inner {
98            RdfSerializerKind::JsonLd(_) => RdfFormat::JsonLd {
99                profile: JsonLdProfile::Streaming.into(), // TODO: also expanded?
100            },
101            RdfSerializerKind::NQuads(_) => RdfFormat::NQuads,
102            RdfSerializerKind::NTriples(_) => RdfFormat::NTriples,
103            RdfSerializerKind::RdfXml(_) => RdfFormat::RdfXml,
104            RdfSerializerKind::TriG(_) => RdfFormat::TriG,
105            RdfSerializerKind::Turtle(_) => RdfFormat::Turtle,
106        }
107    }
108
109    /// If the format supports it, sets a prefix.
110    ///
111    /// ```
112    /// use oxrdf::vocab::rdf;
113    /// use oxrdf::{NamedNodeRef, TripleRef};
114    /// use oxrdfio::{RdfFormat, RdfSerializer};
115    ///
116    /// let mut serializer = RdfSerializer::from_format(RdfFormat::Turtle)
117    ///     .with_prefix("schema", "http://schema.org/")?
118    ///     .for_writer(Vec::new());
119    /// serializer.serialize_triple(TripleRef {
120    ///     subject: NamedNodeRef::new("http://example.com/s")?.into(),
121    ///     predicate: rdf::TYPE.into(),
122    ///     object: NamedNodeRef::new("http://schema.org/Person")?.into(),
123    /// })?;
124    /// assert_eq!(
125    ///     serializer.finish()?,
126    ///     b"@prefix schema: <http://schema.org/> .\n<http://example.com/s> a schema:Person .\n"
127    /// );
128    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
129    /// ```
130    #[inline]
131    pub fn with_prefix(
132        mut self,
133        prefix_name: impl Into<String>,
134        prefix_iri: impl Into<String>,
135    ) -> Result<Self, IriParseError> {
136        self.inner = match self.inner {
137            RdfSerializerKind::JsonLd(s) => RdfSerializerKind::JsonLd(s),
138            RdfSerializerKind::NQuads(s) => RdfSerializerKind::NQuads(s),
139            RdfSerializerKind::NTriples(s) => RdfSerializerKind::NTriples(s),
140            RdfSerializerKind::RdfXml(s) => {
141                RdfSerializerKind::RdfXml(s.with_prefix(prefix_name, prefix_iri)?)
142            }
143            RdfSerializerKind::TriG(s) => {
144                RdfSerializerKind::TriG(s.with_prefix(prefix_name, prefix_iri)?)
145            }
146            RdfSerializerKind::Turtle(s) => {
147                RdfSerializerKind::Turtle(s.with_prefix(prefix_name, prefix_iri)?)
148            }
149        };
150        Ok(self)
151    }
152
153    /// If the format supports it, sets a base IRI.
154    ///
155    /// ```
156    /// use oxrdf::vocab::rdf;
157    /// use oxrdf::{NamedNodeRef, TripleRef};
158    /// use oxrdfio::{RdfFormat, RdfSerializer};
159    ///
160    /// let mut serializer = RdfSerializer::from_format(RdfFormat::Turtle)
161    ///     .with_base_iri("http://example.com")?
162    ///     .with_prefix("ex", "http://example.com/ns#")?
163    ///     .for_writer(Vec::new());
164    /// serializer.serialize_triple(TripleRef::new(
165    ///     NamedNodeRef::new("http://example.com/me")?,
166    ///     rdf::TYPE,
167    ///     NamedNodeRef::new("http://example.com/ns#Person")?,
168    /// ))?;
169    /// assert_eq!(
170    ///     serializer.finish()?,
171    ///     b"@base <http://example.com> .\n@prefix ex: </ns#> .\n</me> a ex:Person .\n",
172    /// );
173    /// # Result::<_,Box<dyn std::error::Error>>::Ok(())
174    /// ```
175    #[inline]
176    pub fn with_base_iri(mut self, base_iri: impl Into<String>) -> Result<Self, IriParseError> {
177        self.inner = match self.inner {
178            RdfSerializerKind::JsonLd(s) => RdfSerializerKind::JsonLd(s),
179            RdfSerializerKind::NQuads(s) => RdfSerializerKind::NQuads(s),
180            RdfSerializerKind::NTriples(s) => RdfSerializerKind::NTriples(s),
181            RdfSerializerKind::RdfXml(s) => RdfSerializerKind::RdfXml(s.with_base_iri(base_iri)?),
182            RdfSerializerKind::TriG(s) => RdfSerializerKind::TriG(s.with_base_iri(base_iri)?),
183            RdfSerializerKind::Turtle(s) => RdfSerializerKind::Turtle(s.with_base_iri(base_iri)?),
184        };
185        Ok(self)
186    }
187
188    /// Serializes to a [`Write`] implementation.
189    ///
190    /// <div class="warning">
191    ///
192    /// Do not forget to run the [`finish`](WriterQuadSerializer::finish()) method to properly write the last bytes of the file.</div>
193    ///
194    /// <div class="warning">
195    ///
196    /// This writer does unbuffered writes. You might want to use [`BufWriter`](io::BufWriter) to avoid that.</div>
197    ///
198    /// ```
199    /// use oxrdfio::{RdfFormat, RdfSerializer};
200    /// use oxrdf::{Quad, NamedNode};
201    ///
202    /// let mut serializer = RdfSerializer::from_format(RdfFormat::NQuads).for_writer(Vec::new());
203    /// serializer.serialize_quad(&Quad {
204    ///    subject: NamedNode::new("http://example.com/s")?.into(),
205    ///    predicate: NamedNode::new("http://example.com/p")?,
206    ///    object: NamedNode::new("http://example.com/o")?.into(),
207    ///    graph_name: NamedNode::new("http://example.com/g")?.into()
208    /// })?;
209    /// assert_eq!(serializer.finish()?, b"<http://example.com/s> <http://example.com/p> <http://example.com/o> <http://example.com/g> .\n");
210    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
211    /// ```
212    pub fn for_writer<W: Write>(self, writer: W) -> WriterQuadSerializer<W> {
213        WriterQuadSerializer {
214            inner: match self.inner {
215                RdfSerializerKind::JsonLd(s) => {
216                    WriterQuadSerializerKind::JsonLd(s.for_writer(writer))
217                }
218                RdfSerializerKind::NQuads(s) => {
219                    WriterQuadSerializerKind::NQuads(s.for_writer(writer))
220                }
221                RdfSerializerKind::NTriples(s) => {
222                    WriterQuadSerializerKind::NTriples(s.for_writer(writer))
223                }
224                RdfSerializerKind::RdfXml(s) => {
225                    WriterQuadSerializerKind::RdfXml(s.for_writer(writer))
226                }
227                RdfSerializerKind::TriG(s) => WriterQuadSerializerKind::TriG(s.for_writer(writer)),
228                RdfSerializerKind::Turtle(s) => {
229                    WriterQuadSerializerKind::Turtle(s.for_writer(writer))
230                }
231            },
232        }
233    }
234
235    /// Serializes to a Tokio [`AsyncWrite`] implementation.
236    ///
237    /// <div class="warning">
238    ///
239    /// Do not forget to run the [`finish`](TokioAsyncWriterQuadSerializer::finish()) method to properly write the last bytes of the file.</div>
240    ///
241    /// <div class="warning">
242    ///
243    /// This writer does unbuffered writes. You might want to use [`BufWriter`](tokio::io::BufWriter) to avoid that.</div>
244    ///
245    /// ```
246    /// # #[tokio::main(flavor = "current_thread")]
247    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
248    /// use oxrdfio::{RdfFormat, RdfSerializer};
249    /// use oxrdf::{Quad, NamedNode};
250    ///
251    /// let mut serializer = RdfSerializer::from_format(RdfFormat::NQuads).for_tokio_async_writer(Vec::new());
252    /// serializer.serialize_quad(&Quad {
253    ///     subject: NamedNode::new("http://example.com/s")?.into(),
254    ///     predicate: NamedNode::new("http://example.com/p")?,
255    ///     object: NamedNode::new("http://example.com/o")?.into(),
256    ///     graph_name: NamedNode::new("http://example.com/g")?.into()
257    /// }).await?;
258    /// assert_eq!(serializer.finish().await?, b"<http://example.com/s> <http://example.com/p> <http://example.com/o> <http://example.com/g> .\n");
259    /// # Ok(())
260    /// # }
261    /// ```
262    #[cfg(feature = "async-tokio")]
263    pub fn for_tokio_async_writer<W: AsyncWrite + Unpin>(
264        self,
265        writer: W,
266    ) -> TokioAsyncWriterQuadSerializer<W> {
267        TokioAsyncWriterQuadSerializer {
268            inner: match self.inner {
269                RdfSerializerKind::JsonLd(s) => {
270                    TokioAsyncWriterQuadSerializerKind::JsonLd(s.for_tokio_async_writer(writer))
271                }
272                RdfSerializerKind::NQuads(s) => {
273                    TokioAsyncWriterQuadSerializerKind::NQuads(s.for_tokio_async_writer(writer))
274                }
275                RdfSerializerKind::NTriples(s) => {
276                    TokioAsyncWriterQuadSerializerKind::NTriples(s.for_tokio_async_writer(writer))
277                }
278                RdfSerializerKind::RdfXml(s) => {
279                    TokioAsyncWriterQuadSerializerKind::RdfXml(s.for_tokio_async_writer(writer))
280                }
281                RdfSerializerKind::TriG(s) => {
282                    TokioAsyncWriterQuadSerializerKind::TriG(s.for_tokio_async_writer(writer))
283                }
284                RdfSerializerKind::Turtle(s) => {
285                    TokioAsyncWriterQuadSerializerKind::Turtle(s.for_tokio_async_writer(writer))
286                }
287            },
288        }
289    }
290}
291
292impl From<RdfFormat> for RdfSerializer {
293    fn from(format: RdfFormat) -> Self {
294        Self::from_format(format)
295    }
296}
297
298/// Serializes quads or triples to a [`Write`] implementation.
299///
300/// Can be built using [`RdfSerializer::for_writer`].
301///
302/// <div class="warning">
303///
304/// Do not forget to run the [`finish`](WriterQuadSerializer::finish()) method to properly write the last bytes of the file.</div>
305///
306/// <div class="warning">
307///
308/// This writer does unbuffered writes. You might want to use [`BufWriter`](io::BufWriter) to avoid that.</div>
309///
310/// ```
311/// use oxrdfio::{RdfFormat, RdfSerializer};
312/// use oxrdf::{Quad, NamedNode};
313///
314/// let mut serializer = RdfSerializer::from_format(RdfFormat::NQuads).for_writer(Vec::new());
315/// serializer.serialize_quad(&Quad {
316///    subject: NamedNode::new("http://example.com/s")?.into(),
317///    predicate: NamedNode::new("http://example.com/p")?,
318///    object: NamedNode::new("http://example.com/o")?.into(),
319///    graph_name: NamedNode::new("http://example.com/g")?.into(),
320/// })?;
321/// assert_eq!(serializer.finish()?, b"<http://example.com/s> <http://example.com/p> <http://example.com/o> <http://example.com/g> .\n");
322/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
323/// ```
324#[must_use]
325pub struct WriterQuadSerializer<W: Write> {
326    inner: WriterQuadSerializerKind<W>,
327}
328
329enum WriterQuadSerializerKind<W: Write> {
330    JsonLd(WriterJsonLdSerializer<W>),
331    NQuads(WriterNQuadsSerializer<W>),
332    NTriples(WriterNTriplesSerializer<W>),
333    RdfXml(WriterRdfXmlSerializer<W>),
334    TriG(WriterTriGSerializer<W>),
335    Turtle(WriterTurtleSerializer<W>),
336}
337
338impl<W: Write> WriterQuadSerializer<W> {
339    /// Serializes a [`QuadRef`]
340    pub fn serialize_quad<'a>(&mut self, quad: impl Into<QuadRef<'a>>) -> io::Result<()> {
341        match &mut self.inner {
342            WriterQuadSerializerKind::JsonLd(serializer) => serializer.serialize_quad(quad),
343            WriterQuadSerializerKind::NQuads(serializer) => serializer.serialize_quad(quad),
344            WriterQuadSerializerKind::NTriples(serializer) => {
345                serializer.serialize_triple(to_triple(quad)?)
346            }
347            WriterQuadSerializerKind::RdfXml(serializer) => {
348                serializer.serialize_triple(to_triple(quad)?)
349            }
350            WriterQuadSerializerKind::TriG(serializer) => serializer.serialize_quad(quad),
351            WriterQuadSerializerKind::Turtle(serializer) => {
352                serializer.serialize_triple(to_triple(quad)?)
353            }
354        }
355    }
356
357    /// Serializes a [`TripleRef`]
358    pub fn serialize_triple<'a>(&mut self, triple: impl Into<TripleRef<'a>>) -> io::Result<()> {
359        self.serialize_quad(triple.into().in_graph(GraphNameRef::DefaultGraph))
360    }
361
362    /// Writes the last bytes of the file
363    ///
364    /// Note that this function does not flush the writer. You need to do that if you are using a [`BufWriter`](io::BufWriter).
365    pub fn finish(self) -> io::Result<W> {
366        Ok(match self.inner {
367            WriterQuadSerializerKind::JsonLd(serializer) => serializer.finish()?,
368            WriterQuadSerializerKind::NQuads(serializer) => serializer.finish(),
369            WriterQuadSerializerKind::NTriples(serializer) => serializer.finish(),
370            WriterQuadSerializerKind::RdfXml(serializer) => serializer.finish()?,
371            WriterQuadSerializerKind::TriG(serializer) => serializer.finish()?,
372            WriterQuadSerializerKind::Turtle(serializer) => serializer.finish()?,
373        })
374    }
375}
376
377/// Serializes quads or triples to a [`AsyncWrite`] implementation.
378///
379/// Can be built using [`RdfSerializer::for_tokio_async_writer`].
380///
381/// <div class="warning">
382///
383/// Do not forget to run the [`finish`](WriterQuadSerializer::finish()) method to properly write the last bytes of the file.</div>
384///
385/// <div class="warning">
386///
387/// This writer does unbuffered writes. You might want to use [`BufWriter`](io::BufWriter) to avoid that.</div>
388///
389/// ```
390/// # #[tokio::main(flavor = "current_thread")]
391/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
392/// use oxrdfio::{RdfFormat, RdfSerializer};
393/// use oxrdf::{Quad, NamedNode};
394///
395/// let mut serializer = RdfSerializer::from_format(RdfFormat::NQuads).for_tokio_async_writer(Vec::new());
396/// serializer.serialize_quad(&Quad {
397///     subject: NamedNode::new("http://example.com/s")?.into(),
398///     predicate: NamedNode::new("http://example.com/p")?,
399///     object: NamedNode::new("http://example.com/o")?.into(),
400///     graph_name: NamedNode::new("http://example.com/g")?.into()
401/// }).await?;
402/// assert_eq!(serializer.finish().await?, b"<http://example.com/s> <http://example.com/p> <http://example.com/o> <http://example.com/g> .\n");
403/// # Ok(())
404/// # }
405/// ```
406#[must_use]
407#[cfg(feature = "async-tokio")]
408pub struct TokioAsyncWriterQuadSerializer<W: AsyncWrite + Unpin> {
409    inner: TokioAsyncWriterQuadSerializerKind<W>,
410}
411
412#[cfg(feature = "async-tokio")]
413enum TokioAsyncWriterQuadSerializerKind<W: AsyncWrite + Unpin> {
414    JsonLd(TokioAsyncWriterJsonLdSerializer<W>),
415    NQuads(TokioAsyncWriterNQuadsSerializer<W>),
416    NTriples(TokioAsyncWriterNTriplesSerializer<W>),
417    RdfXml(TokioAsyncWriterRdfXmlSerializer<W>),
418    TriG(TokioAsyncWriterTriGSerializer<W>),
419    Turtle(TokioAsyncWriterTurtleSerializer<W>),
420}
421
422#[cfg(feature = "async-tokio")]
423impl<W: AsyncWrite + Unpin> TokioAsyncWriterQuadSerializer<W> {
424    /// Serializes a [`QuadRef`]
425    pub async fn serialize_quad<'a>(&mut self, quad: impl Into<QuadRef<'a>>) -> io::Result<()> {
426        match &mut self.inner {
427            TokioAsyncWriterQuadSerializerKind::JsonLd(serializer) => {
428                serializer.serialize_quad(quad).await
429            }
430            TokioAsyncWriterQuadSerializerKind::NQuads(serializer) => {
431                serializer.serialize_quad(quad).await
432            }
433            TokioAsyncWriterQuadSerializerKind::NTriples(serializer) => {
434                serializer.serialize_triple(to_triple(quad)?).await
435            }
436            TokioAsyncWriterQuadSerializerKind::RdfXml(serializer) => {
437                serializer.serialize_triple(to_triple(quad)?).await
438            }
439            TokioAsyncWriterQuadSerializerKind::TriG(serializer) => {
440                serializer.serialize_quad(quad).await
441            }
442            TokioAsyncWriterQuadSerializerKind::Turtle(serializer) => {
443                serializer.serialize_triple(to_triple(quad)?).await
444            }
445        }
446    }
447
448    /// Serializes a [`TripleRef`]
449    pub async fn serialize_triple<'a>(
450        &mut self,
451        triple: impl Into<TripleRef<'a>>,
452    ) -> io::Result<()> {
453        self.serialize_quad(triple.into().in_graph(GraphNameRef::DefaultGraph))
454            .await
455    }
456
457    /// Writes the last bytes of the file
458    ///
459    /// Note that this function does not flush the writer. You need to do that if you are using a [`BufWriter`](io::BufWriter).
460    pub async fn finish(self) -> io::Result<W> {
461        Ok(match self.inner {
462            TokioAsyncWriterQuadSerializerKind::JsonLd(serializer) => serializer.finish().await?,
463            TokioAsyncWriterQuadSerializerKind::NQuads(serializer) => serializer.finish(),
464            TokioAsyncWriterQuadSerializerKind::NTriples(serializer) => serializer.finish(),
465            TokioAsyncWriterQuadSerializerKind::RdfXml(serializer) => serializer.finish().await?,
466            TokioAsyncWriterQuadSerializerKind::TriG(serializer) => serializer.finish().await?,
467            TokioAsyncWriterQuadSerializerKind::Turtle(serializer) => serializer.finish().await?,
468        })
469    }
470}
471
472fn to_triple<'a>(quad: impl Into<QuadRef<'a>>) -> io::Result<TripleRef<'a>> {
473    let quad = quad.into();
474    if quad.graph_name.is_default_graph() {
475        Ok(quad.into())
476    } else {
477        Err(io::Error::new(
478            io::ErrorKind::InvalidInput,
479            "Only quads in the default graph can be serialized to a RDF graph format",
480        ))
481    }
482}