From 217abaf7ee80b2a960f6e33fee87c4aa31476039 Mon Sep 17 00:00:00 2001 From: Tpt Date: Sat, 12 Aug 2023 16:30:48 +0200 Subject: [PATCH] Adopt new I/O API for serialization --- js/src/store.rs | 30 +++----- lib/src/io/mod.rs | 1 + lib/src/io/write.rs | 4 + lib/src/sparql/error.rs | 11 ++- lib/src/sparql/model.rs | 13 ++-- lib/src/sparql/update.rs | 21 ++--- lib/src/storage/error.rs | 12 ++- lib/src/store.rs | 24 +++--- lib/tests/store.rs | 10 +-- python/src/io.rs | 45 +++++------ python/src/sparql.rs | 4 +- python/src/store.rs | 36 ++++----- python/tests/test_store.py | 4 +- server/src/main.rs | 154 ++++++++++++++++++++----------------- 14 files changed, 189 insertions(+), 180 deletions(-) diff --git a/js/src/store.rs b/js/src/store.rs index 9fce396d..ef8673bc 100644 --- a/js/src/store.rs +++ b/js/src/store.rs @@ -4,7 +4,7 @@ use crate::format_err; use crate::model::*; use crate::utils::to_err; use js_sys::{Array, Map}; -use oxigraph::io::{DatasetFormat, GraphFormat}; +use oxigraph::io::{DatasetFormat, GraphFormat, RdfFormat}; use oxigraph::model::*; use oxigraph::sparql::QueryResults; use oxigraph::store::Store; @@ -191,34 +191,22 @@ impl JsStore { } pub fn dump(&self, mime_type: &str, from_graph_name: &JsValue) -> Result { + let Some(format) = RdfFormat::from_media_type(mime_type) else { + return Err(format_err!("Not supported MIME type: {mime_type}")); + }; let from_graph_name = if let Some(graph_name) = FROM_JS.with(|c| c.to_optional_term(from_graph_name))? { - Some(graph_name.try_into()?) + Some(GraphName::try_from(graph_name)?) } else { None }; - let mut buffer = Vec::new(); - if let Some(graph_format) = GraphFormat::from_media_type(mime_type) { - self.store - .dump_graph( - &mut buffer, - graph_format, - &from_graph_name.unwrap_or(GraphName::DefaultGraph), - ) - .map_err(to_err)?; - } else if let Some(dataset_format) = DatasetFormat::from_media_type(mime_type) { - if from_graph_name.is_some() { - return Err(format_err!( - "The target graph name parameter is not available for dataset formats" - )); - } - self.store - .dump_dataset(&mut buffer, dataset_format) - .map_err(to_err)?; + if let Some(from_graph_name) = &from_graph_name { + self.store.dump_graph(&mut buffer, format, from_graph_name) } else { - return Err(format_err!("Not supported MIME type: {mime_type}")); + self.store.dump_dataset(&mut buffer, format) } + .map_err(to_err)?; String::from_utf8(buffer).map_err(to_err) } } diff --git a/lib/src/io/mod.rs b/lib/src/io/mod.rs index a8185918..9d91c881 100644 --- a/lib/src/io/mod.rs +++ b/lib/src/io/mod.rs @@ -7,6 +7,7 @@ pub mod write; pub use self::format::{DatasetFormat, GraphFormat}; pub use self::read::{DatasetParser, GraphParser}; +#[allow(deprecated)] pub use self::write::{DatasetSerializer, GraphSerializer}; pub use oxrdfio::{ FromReadQuadReader, ParseError, RdfFormat, RdfParser, RdfSerializer, SyntaxError, diff --git a/lib/src/io/write.rs b/lib/src/io/write.rs index b9373afc..7955f3a3 100644 --- a/lib/src/io/write.rs +++ b/lib/src/io/write.rs @@ -1,3 +1,5 @@ +#![allow(deprecated)] + //! Utilities to write RDF graphs and datasets. use crate::io::{DatasetFormat, GraphFormat}; @@ -28,6 +30,7 @@ use std::io::{self, Write}; /// assert_eq!(buffer.as_slice(), " .\n".as_bytes()); /// # Result::<_,Box>::Ok(()) /// ``` +#[deprecated(note = "Use RdfSerializer instead")] pub struct GraphSerializer { inner: RdfSerializer, } @@ -110,6 +113,7 @@ impl TripleWriter { /// assert_eq!(buffer.as_slice(), " .\n".as_bytes()); /// # Result::<_,Box>::Ok(()) /// ``` +#[deprecated(note = "Use RdfSerializer instead")] pub struct DatasetSerializer { inner: RdfSerializer, } diff --git a/lib/src/sparql/error.rs b/lib/src/sparql/error.rs index 101b0165..62e5d821 100644 --- a/lib/src/sparql/error.rs +++ b/lib/src/sparql/error.rs @@ -1,4 +1,4 @@ -use crate::io::read::ParseError; +use crate::io::{ParseError, SyntaxError}; use crate::storage::StorageError; use std::convert::Infallible; use std::error; @@ -14,10 +14,10 @@ pub enum EvaluationError { /// An error from the storage. Storage(StorageError), /// An error while parsing an external RDF file. - GraphParsing(ParseError), + GraphParsing(SyntaxError), /// An error while parsing an external result file (likely from a federated query). ResultsParsing(sparesults::ParseError), - /// An error returned during store IOs or during results write. + /// An error returned during store or results I/Os. Io(io::Error), /// An error returned during the query evaluation itself (not supported custom function...). Query(QueryError), @@ -132,7 +132,10 @@ impl From for EvaluationError { impl From for EvaluationError { #[inline] fn from(error: ParseError) -> Self { - Self::GraphParsing(error) + match error { + ParseError::Syntax(error) => Self::GraphParsing(error), + ParseError::Io(error) => Self::Io(error), + } } } diff --git a/lib/src/sparql/model.rs b/lib/src/sparql/model.rs index 1aec94a1..aa7c83fe 100644 --- a/lib/src/sparql/model.rs +++ b/lib/src/sparql/model.rs @@ -1,5 +1,4 @@ -use crate::io::GraphFormat; -use crate::io::GraphSerializer; +use crate::io::{RdfFormat, RdfSerializer}; use crate::model::*; use crate::sparql::error::EvaluationError; use oxrdf::{Variable, VariableRef}; @@ -96,7 +95,7 @@ impl QueryResults { /// /// ``` /// use oxigraph::store::Store; - /// use oxigraph::io::GraphFormat; + /// use oxigraph::io::{RdfFormat, GraphFormat}; /// use oxigraph::model::*; /// /// let graph = " .\n"; @@ -105,19 +104,19 @@ impl QueryResults { /// store.load_graph(graph.as_bytes(), GraphFormat::NTriples, GraphNameRef::DefaultGraph, None)?; /// /// let mut results = Vec::new(); - /// store.query("CONSTRUCT WHERE { ?s ?p ?o }")?.write_graph(&mut results, GraphFormat::NTriples)?; + /// store.query("CONSTRUCT WHERE { ?s ?p ?o }")?.write_graph(&mut results, RdfFormat::NTriples)?; /// assert_eq!(results, graph.as_bytes()); /// # Result::<_,Box>::Ok(()) /// ``` pub fn write_graph( self, write: impl Write, - format: GraphFormat, + format: impl Into, ) -> Result<(), EvaluationError> { if let Self::Graph(triples) = self { - let mut writer = GraphSerializer::from_format(format).triple_writer(write); + let mut writer = RdfSerializer::from_format(format.into()).serialize_to_write(write); for triple in triples { - writer.write(&triple?)?; + writer.write_triple(&triple?)?; } writer.finish()?; Ok(()) diff --git a/lib/src/sparql/update.rs b/lib/src/sparql/update.rs index a62f99a8..ef4f9c54 100644 --- a/lib/src/sparql/update.rs +++ b/lib/src/sparql/update.rs @@ -1,5 +1,4 @@ -use crate::io::read::ParseError; -use crate::io::{GraphFormat, GraphParser}; +use crate::io::{RdfFormat, RdfParser}; use crate::model::{GraphName as OxGraphName, GraphNameRef, Quad as OxQuad}; use crate::sparql::algebra::QueryDataset; use crate::sparql::dataset::DatasetView; @@ -166,7 +165,7 @@ impl<'a, 'b: 'a> SimpleUpdateEvaluator<'a, 'b> { from.as_str(), "application/n-triples, text/turtle, application/rdf+xml", )?; - let format = GraphFormat::from_media_type(&content_type).ok_or_else(|| { + let format = RdfFormat::from_media_type(&content_type).ok_or_else(|| { EvaluationError::msg(format!( "Unsupported Content-Type returned by {from}: {content_type}" )) @@ -175,15 +174,17 @@ impl<'a, 'b: 'a> SimpleUpdateEvaluator<'a, 'b> { GraphName::NamedNode(graph_name) => graph_name.into(), GraphName::DefaultGraph => GraphNameRef::DefaultGraph, }; - let mut parser = GraphParser::from_format(format); + let mut parser = RdfParser::from_format(format) + .rename_blank_nodes() + .without_named_graphs() + .with_default_graph(to_graph_name); if let Some(base_iri) = &self.base_iri { - parser = parser - .with_base_iri(base_iri.as_str()) - .map_err(|e| ParseError::invalid_base_iri(base_iri, e))?; + parser = parser.with_base_iri(base_iri.as_str()).map_err(|e| { + EvaluationError::msg(format!("The LOAD IRI '{base_iri}' is invalid: {e}")) + })?; } - for t in parser.read_triples(body) { - self.transaction - .insert(t?.as_ref().in_graph(to_graph_name))?; + for q in parser.parse_read(body) { + self.transaction.insert(q?.as_ref())?; } Ok(()) } diff --git a/lib/src/storage/error.rs b/lib/src/storage/error.rs index f0d5b841..754513eb 100644 --- a/lib/src/storage/error.rs +++ b/lib/src/storage/error.rs @@ -1,4 +1,4 @@ -use crate::io::read::ParseError; +use crate::io::{read::ParseError, RdfFormat}; use std::error::Error; use std::fmt; use std::io; @@ -179,6 +179,8 @@ pub enum SerializerError { Io(io::Error), /// An error raised during the lookup in the store. Storage(StorageError), + /// A format compatible with [RDF dataset](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-dataset) is required. + DatasetFormatExpected(RdfFormat), } impl fmt::Display for SerializerError { @@ -187,6 +189,10 @@ impl fmt::Display for SerializerError { match self { Self::Io(e) => e.fmt(f), Self::Storage(e) => e.fmt(f), + Self::DatasetFormatExpected(format) => write!( + f, + "A RDF format supporting datasets was expected, {format} found" + ), } } } @@ -197,6 +203,7 @@ impl Error for SerializerError { match self { Self::Io(e) => Some(e), Self::Storage(e) => Some(e), + Self::DatasetFormatExpected(_) => None, } } } @@ -221,6 +228,9 @@ impl From for io::Error { match error { SerializerError::Storage(error) => error.into(), SerializerError::Io(error) => error, + SerializerError::DatasetFormatExpected(_) => { + io::Error::new(io::ErrorKind::InvalidInput, error.to_string()) + } } } } diff --git a/lib/src/store.rs b/lib/src/store.rs index eed5dce2..3923bf80 100644 --- a/lib/src/store.rs +++ b/lib/src/store.rs @@ -24,9 +24,7 @@ //! # Result::<_, Box>::Ok(()) //! ``` use crate::io::read::ParseError; -use crate::io::{ - DatasetFormat, DatasetParser, DatasetSerializer, GraphFormat, GraphParser, GraphSerializer, -}; +use crate::io::{DatasetFormat, DatasetParser, GraphFormat, GraphParser, RdfFormat, RdfSerializer}; use crate::model::*; use crate::sparql::{ evaluate_query, evaluate_update, EvaluationError, Query, QueryExplanation, QueryOptions, @@ -612,13 +610,13 @@ impl Store { /// ``` pub fn dump_graph<'a>( &self, - writer: impl Write, - format: GraphFormat, + write: impl Write, + format: impl Into, from_graph_name: impl Into>, ) -> Result<(), SerializerError> { - let mut writer = GraphSerializer::from_format(format).triple_writer(writer); + let mut writer = RdfSerializer::from_format(format.into()).serialize_to_write(write); for quad in self.quads_for_pattern(None, None, None, Some(from_graph_name.into())) { - writer.write(quad?.as_ref())?; + writer.write_triple(quad?.as_ref())?; } writer.finish()?; Ok(()) @@ -642,12 +640,16 @@ impl Store { /// ``` pub fn dump_dataset( &self, - writer: impl Write, - format: DatasetFormat, + write: impl Write, + format: impl Into, ) -> Result<(), SerializerError> { - let mut writer = DatasetSerializer::from_format(format).quad_writer(writer); + let format = format.into(); + if !format.supports_datasets() { + return Err(SerializerError::DatasetFormatExpected(format)); + } + let mut writer = RdfSerializer::from_format(format).serialize_to_write(write); for quad in self.iter() { - writer.write(&quad?)?; + writer.write_quad(&quad?)?; } writer.finish()?; Ok(()) diff --git a/lib/tests/store.rs b/lib/tests/store.rs index 7328ef4a..750d74c8 100644 --- a/lib/tests/store.rs +++ b/lib/tests/store.rs @@ -1,4 +1,4 @@ -use oxigraph::io::{DatasetFormat, GraphFormat}; +use oxigraph::io::{DatasetFormat, GraphFormat, RdfFormat}; use oxigraph::model::vocab::{rdf, xsd}; use oxigraph::model::*; use oxigraph::store::Store; @@ -211,11 +211,7 @@ fn test_dump_graph() -> Result<(), Box> { } let mut buffer = Vec::new(); - store.dump_graph( - &mut buffer, - GraphFormat::NTriples, - GraphNameRef::DefaultGraph, - )?; + store.dump_graph(&mut buffer, RdfFormat::NTriples, GraphNameRef::DefaultGraph)?; assert_eq!( buffer.into_iter().filter(|c| *c == b'\n').count(), NUMBER_OF_TRIPLES @@ -231,7 +227,7 @@ fn test_dump_dataset() -> Result<(), Box> { } let mut buffer = Vec::new(); - store.dump_dataset(&mut buffer, DatasetFormat::NQuads)?; + store.dump_dataset(&mut buffer, RdfFormat::NQuads)?; assert_eq!( buffer.into_iter().filter(|c| *c == b'\n').count(), NUMBER_OF_TRIPLES diff --git a/python/src/io.rs b/python/src/io.rs index 28245b39..9b6d4075 100644 --- a/python/src/io.rs +++ b/python/src/io.rs @@ -3,8 +3,9 @@ use crate::model::{PyQuad, PyTriple}; use oxigraph::io::read::{ParseError, QuadReader, TripleReader}; use oxigraph::io::{ - DatasetFormat, DatasetParser, DatasetSerializer, GraphFormat, GraphParser, GraphSerializer, + DatasetFormat, DatasetParser, GraphFormat, GraphParser, RdfFormat, RdfSerializer, }; +use oxigraph::model::QuadRef; use pyo3::exceptions::{PyIOError, PySyntaxError, PyValueError}; use pyo3::prelude::*; use pyo3::types::PyBytes; @@ -120,34 +121,34 @@ pub fn parse( /// b' "1" .\n' #[pyfunction] pub fn serialize(input: &PyAny, output: PyObject, mime_type: &str, py: Python<'_>) -> PyResult<()> { + let Some(format) = RdfFormat::from_media_type(mime_type) else { + return Err(PyValueError::new_err(format!( + "Not supported MIME type: {mime_type}" + ))); + }; let output = if let Ok(path) = output.extract::(py) { PyWritable::from_file(&path, py).map_err(map_io_err)? } else { PyWritable::from_data(output) }; - if let Some(graph_format) = GraphFormat::from_media_type(mime_type) { - let mut writer = GraphSerializer::from_format(graph_format).triple_writer(output); - for i in input.iter()? { - writer - .write(&*i?.extract::>()?) - .map_err(map_io_err)?; - } - writer.finish().map_err(map_io_err)?; - Ok(()) - } else if let Some(dataset_format) = DatasetFormat::from_media_type(mime_type) { - let mut writer = DatasetSerializer::from_format(dataset_format).quad_writer(output); - for i in input.iter()? { - writer - .write(&*i?.extract::>()?) - .map_err(map_io_err)?; + let mut writer = RdfSerializer::from_format(format).serialize_to_write(output); + for i in input.iter()? { + let i = i?; + if let Ok(triple) = i.extract::>() { + writer.write_triple(&*triple) + } else { + let quad = i.extract::>()?; + let quad = QuadRef::from(&*quad); + if !quad.graph_name.is_default_graph() && !format.supports_datasets() { + return Err(PyValueError::new_err( + "The {format} format does not support named graphs", + )); + } + writer.write_quad(quad) } - writer.finish().map_err(map_io_err)?; - Ok(()) - } else { - Err(PyValueError::new_err(format!( - "Not supported MIME type: {mime_type}" - ))) + .map_err(map_io_err)?; } + writer.finish().map_err(map_io_err) } #[pyclass(name = "TripleReader", module = "pyoxigraph")] diff --git a/python/src/sparql.rs b/python/src/sparql.rs index 01298fa6..408f6f0c 100644 --- a/python/src/sparql.rs +++ b/python/src/sparql.rs @@ -1,4 +1,4 @@ -use crate::io::{allow_threads_unsafe, map_io_err, map_parse_error}; +use crate::io::{allow_threads_unsafe, map_io_err}; use crate::map_storage_error; use crate::model::*; use oxigraph::model::Term; @@ -234,7 +234,7 @@ pub fn map_evaluation_error(error: EvaluationError) -> PyErr { EvaluationError::Parsing(error) => PySyntaxError::new_err(error.to_string()), EvaluationError::Storage(error) => map_storage_error(error), EvaluationError::Io(error) => map_io_err(error), - EvaluationError::GraphParsing(error) => map_parse_error(error), + EvaluationError::GraphParsing(error) => PySyntaxError::new_err(error.to_string()), EvaluationError::Query(error) => PyValueError::new_err(error.to_string()), _ => PyRuntimeError::new_err(error.to_string()), } diff --git a/python/src/store.rs b/python/src/store.rs index 957c1b5c..692b485f 100644 --- a/python/src/store.rs +++ b/python/src/store.rs @@ -3,7 +3,7 @@ use crate::io::{allow_threads_unsafe, map_io_err, map_parse_error, PyReadable, PyWritable}; use crate::model::*; use crate::sparql::*; -use oxigraph::io::{DatasetFormat, GraphFormat}; +use oxigraph::io::{DatasetFormat, GraphFormat, RdfFormat}; use oxigraph::model::{GraphName, GraphNameRef}; use oxigraph::sparql::Update; use oxigraph::store::{self, LoaderError, SerializerError, StorageError, Store}; @@ -522,10 +522,10 @@ impl PyStore { /// :type output: io(bytes) or str or pathlib.Path /// :param mime_type: the MIME type of the RDF serialization. /// :type mime_type: str - /// :param from_graph: if a triple based format is requested, the store graph from which dump the triples. By default, the default graph is used. + /// :param from_graph: the store graph from which dump the triples. Required if the serialization format does not support named graphs. If it does supports named graphs the full dataset is written. /// :type from_graph: NamedNode or BlankNode or DefaultGraph or None, optional /// :rtype: None - /// :raises ValueError: if the MIME type is not supported or the `from_graph` parameter is given with a quad syntax. + /// :raises ValueError: if the MIME type is not supported or the `from_graph` parameter is not given with a syntax not supporting named graphs. /// :raises IOError: if an I/O error happens during a quad lookup /// /// >>> store = Store() @@ -547,34 +547,23 @@ impl PyStore { } else { PyWritable::from_data(output) }; + let Some(format) = RdfFormat::from_media_type(mime_type) else { + return Err(PyValueError::new_err(format!( + "Not supported MIME type: {mime_type}" + ))); + }; let from_graph_name = if let Some(graph_name) = from_graph { Some(GraphName::from(&PyGraphNameRef::try_from(graph_name)?)) } else { None }; py.allow_threads(|| { - if let Some(graph_format) = GraphFormat::from_media_type(mime_type) { - self.inner - .dump_graph( - output, - graph_format, - &from_graph_name.unwrap_or(GraphName::DefaultGraph), - ) - .map_err(map_serializer_error) - } else if let Some(dataset_format) = DatasetFormat::from_media_type(mime_type) { - if from_graph_name.is_some() { - return Err(PyValueError::new_err( - "The target graph name parameter is not available for dataset formats", - )); - } - self.inner - .dump_dataset(output, dataset_format) - .map_err(map_serializer_error) + if let Some(from_graph_name) = &from_graph_name { + self.inner.dump_graph(output, format, from_graph_name) } else { - Err(PyValueError::new_err(format!( - "Not supported MIME type: {mime_type}" - ))) + self.inner.dump_dataset(output, format) } + .map_err(map_serializer_error) }) } @@ -878,6 +867,7 @@ pub fn map_serializer_error(error: SerializerError) -> PyErr { match error { SerializerError::Storage(error) => map_storage_error(error), SerializerError::Io(error) => PyIOError::new_err(error.to_string()), + SerializerError::DatasetFormatExpected(_) => PyValueError::new_err(error.to_string()), } } diff --git a/python/tests/test_store.py b/python/tests/test_store.py index 287bd27e..ee02e830 100644 --- a/python/tests/test_store.py +++ b/python/tests/test_store.py @@ -321,8 +321,10 @@ class TestStore(unittest.TestCase): ) def test_dump_with_io_error(self) -> None: + store = Store() + store.add(Quad(foo, bar, bar)) with self.assertRaises(OSError) as _, TemporaryFile("rb") as fp: - Store().dump(fp, mime_type="application/rdf+xml") + store.dump(fp, mime_type="application/trig") def test_write_in_read(self) -> None: store = Store() diff --git a/server/src/main.rs b/server/src/main.rs index aed17921..eb9b3116 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,10 +1,10 @@ #![allow(clippy::print_stderr, clippy::cast_precision_loss, clippy::use_debug)] -use anyhow::{anyhow, bail, Context, Error}; +use anyhow::{anyhow, bail, ensure, Context, Error}; use clap::{Parser, Subcommand}; use flate2::read::MultiGzDecoder; use oxhttp::model::{Body, HeaderName, HeaderValue, Method, Request, Response, Status}; use oxhttp::Server; -use oxigraph::io::{DatasetFormat, DatasetSerializer, GraphFormat, GraphSerializer}; +use oxigraph::io::{DatasetFormat, GraphFormat, RdfFormat, RdfSerializer}; use oxigraph::model::{ GraphName, GraphNameRef, IriParseError, NamedNode, NamedNodeRef, NamedOrBlankNode, }; @@ -424,9 +424,9 @@ pub fn main() -> anyhow::Result<()> { .ok_or_else(|| anyhow!("The --location argument is required"))?, )?; let format = if let Some(format) = format { - GraphOrDatasetFormat::from_str(&format)? + rdf_format_from_name(&format)? } else if let Some(file) = &file { - GraphOrDatasetFormat::from_path(file)? + rdf_format_from_path(file)? } else { bail!("The --format option must be set when writing to stdout") }; @@ -554,34 +554,25 @@ pub fn main() -> anyhow::Result<()> { } } QueryResults::Graph(triples) => { - let format = if let Some(name) = results_format { - if let Some(format) = GraphFormat::from_extension(&name) { - format - } else if let Some(format) = GraphFormat::from_media_type(&name) { - format - } else { - bail!("The file format '{name}' is unknown") - } + let format = if let Some(name) = &results_format { + rdf_format_from_name(name) } else if let Some(results_file) = &results_file { - format_from_path(results_file, |ext| { - GraphFormat::from_extension(ext) - .ok_or_else(|| anyhow!("The file extension '{ext}' is unknown")) - })? + rdf_format_from_path(results_file) } else { bail!("The --results-format option must be set when writing to stdout") - }; + }?; + let serializer = RdfSerializer::from_format(format); if let Some(results_file) = results_file { - let mut writer = GraphSerializer::from_format(format) - .triple_writer(BufWriter::new(File::create(results_file)?)); + let mut writer = serializer + .serialize_to_write(BufWriter::new(File::create(results_file)?)); for triple in triples { - writer.write(triple?.as_ref())?; + writer.write_triple(triple?.as_ref())?; } writer.finish()?; } else { - let mut writer = - GraphSerializer::from_format(format).triple_writer(stdout().lock()); + let mut writer = serializer.serialize_to_write(stdout().lock()); for triple in triples { - writer.write(triple?.as_ref())?; + writer.write_triple(triple?.as_ref())?; } writer.finish()?; } @@ -670,22 +661,15 @@ fn bulk_load( fn dump( store: &Store, writer: impl Write, - format: GraphOrDatasetFormat, + format: RdfFormat, to_graph_name: Option, ) -> anyhow::Result<()> { - match format { - GraphOrDatasetFormat::Graph(format) => store.dump_graph( - writer, - format, - &to_graph_name.ok_or_else(|| anyhow!("The --graph option is required when writing a graph format like NTriples, Turtle or RDF/XML"))?, - )?, - GraphOrDatasetFormat::Dataset(format) => { - if to_graph_name.is_some() { - bail!("The --graph option is not allowed when writing a dataset format like NQuads or TriG"); - } - store.dump_dataset(writer, format)? - } - } + ensure!(format.supports_datasets() || to_graph_name.is_some(), "The --graph option is required when writing a format not supporting datasets like NTriples, Turtle or RDF/XML"); + if let Some(to_graph_name) = &to_graph_name { + store.dump_graph(writer, format, to_graph_name) + } else { + store.dump_dataset(writer, format) + }?; Ok(()) } @@ -761,6 +745,23 @@ impl FromStr for GraphOrDatasetFormat { } } +fn rdf_format_from_path(path: &Path) -> anyhow::Result { + format_from_path(path, |ext| { + RdfFormat::from_extension(ext) + .ok_or_else(|| anyhow!("The file extension '{ext}' is unknown")) + }) +} + +fn rdf_format_from_name(name: &str) -> anyhow::Result { + if let Some(t) = RdfFormat::from_extension(name) { + return Ok(t); + } + if let Some(t) = RdfFormat::from_media_type(name) { + return Ok(t); + } + bail!("The file format '{name}' is unknown") +} + fn serve(store: Store, bind: String, read_only: bool, cors: bool) -> anyhow::Result<()> { let mut server = if cors { Server::new(cors_middleware(move |request| { @@ -917,8 +918,9 @@ fn handle_request( (path, "GET") if path.starts_with("/store") => { if let Some(target) = store_target(request)? { assert_that_graph_exists(&store, &target)?; - let format = graph_content_negotiation(request)?; - let triples = store.quads_for_pattern( + let format = rdf_content_negotiation(request)?; + + let quads = store.quads_for_pattern( None, None, None, @@ -927,14 +929,14 @@ fn handle_request( ReadForWrite::build_response( move |w| { Ok(( - GraphSerializer::from_format(format).triple_writer(w), - triples, + RdfSerializer::from_format(format).serialize_to_write(w), + quads, )) }, - |(mut writer, mut triples)| { - Ok(if let Some(t) = triples.next() { - writer.write(&t?.into())?; - Some((writer, triples)) + |(mut writer, mut quads)| { + Ok(if let Some(q) = quads.next() { + writer.write_triple(&q?.into())?; + Some((writer, quads)) } else { writer.finish()?; None @@ -943,17 +945,22 @@ fn handle_request( format.media_type(), ) } else { - let format = dataset_content_negotiation(request)?; + let format = rdf_content_negotiation(request)?; + if !format.supports_datasets() { + return Err(bad_request(format!( + "It is not possible to serialize the full RDF dataset using {format} that does not support named graphs" + ))); + } ReadForWrite::build_response( move |w| { Ok(( - DatasetSerializer::from_format(format).quad_writer(w), + RdfSerializer::from_format(format).serialize_to_write(w), store.iter(), )) }, |(mut writer, mut quads)| { Ok(if let Some(q) = quads.next() { - writer.write(&q?)?; + writer.write_quad(&q?)?; Some((writer, quads)) } else { writer.finish()?; @@ -1227,17 +1234,17 @@ fn evaluate_sparql_query( .with_body(body)) } QueryResults::Graph(triples) => { - let format = graph_content_negotiation(request)?; + let format = rdf_content_negotiation(request)?; ReadForWrite::build_response( move |w| { Ok(( - GraphSerializer::from_format(format).triple_writer(w), + RdfSerializer::from_format(format).serialize_to_write(w), triples, )) }, |(mut writer, mut triples)| { Ok(if let Some(t) = triples.next() { - writer.write(&t?)?; + writer.write_triple(&t?)?; Some((writer, triples)) } else { writer.finish()?; @@ -1403,26 +1410,26 @@ impl From for GraphName { } } -fn graph_content_negotiation(request: &Request) -> Result { - content_negotiation( - request, - &[ - GraphFormat::NTriples.media_type(), - GraphFormat::Turtle.media_type(), - GraphFormat::RdfXml.media_type(), - ], - GraphFormat::from_media_type, - ) -} - -fn dataset_content_negotiation(request: &Request) -> Result { +fn rdf_content_negotiation(request: &Request) -> Result { content_negotiation( request, &[ - DatasetFormat::NQuads.media_type(), - DatasetFormat::TriG.media_type(), + "application/n-quads", + "application/n-triples", + "application/rdf+xml", + "application/trig", + "application/turtle", + "application/xml", + "application/x-trig", + "application/x-turtle", + "text/n3", + "text/nquads", + "text/plain", + "text/turtle", + "text/xml", + "text/x-nquads", ], - DatasetFormat::from_media_type, + RdfFormat::from_media_type, ) } @@ -1430,10 +1437,15 @@ fn query_results_content_negotiation(request: &Request) -> Result