diff --git a/lib/Cargo.toml b/lib/Cargo.toml index ce2b94e3..a1972a8b 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -19,8 +19,8 @@ travis-ci = { repository = "Tpt/rudf" } lazy_static = "1" rocksdb = { version = "0.12", optional = true } uuid = { version = "0.7", features = ["v4"] } -byteorder = {version="1", features = ["i128"] } -quick-xml = "0.15" +byteorder = { version = "1", features = ["i128"] } +quick-xml = { version = "0.16", features = ["failure"] } ordered-float = "1" num-traits = "0.2" rust_decimal = "1" diff --git a/lib/src/sparql/json_results.rs b/lib/src/sparql/json_results.rs index 97dbef17..542ee455 100644 --- a/lib/src/sparql/json_results.rs +++ b/lib/src/sparql/json_results.rs @@ -53,7 +53,7 @@ pub fn write_json_results(results: QueryResult<'_>, mut sink: W) -> Re } Term::BlankNode(bnode) => { sink.write_all(b":{\"type\":\"bnode\",\"value\":")?; - sink.write_fmt(format_args!("{}", bnode.as_uuid().to_simple()))?; + write!(sink, "{}", bnode.as_uuid().to_simple())?; sink.write_all(b"}")?; } Term::Literal(literal) => { @@ -113,7 +113,7 @@ fn write_escaped_json_string(s: &str, sink: &mut impl Write) -> Result<()> { } } } else { - sink.write_fmt(format_args!("{}", c)) + write!(sink, "{}", c) } } }?; diff --git a/lib/src/sparql/model.rs b/lib/src/sparql/model.rs index 067b6721..60ca73c3 100644 --- a/lib/src/sparql/model.rs +++ b/lib/src/sparql/model.rs @@ -1,8 +1,10 @@ use crate::model::*; use crate::sparql::json_results::write_json_results; use crate::sparql::xml_results::{read_xml_results, write_xml_results}; -use crate::{FileSyntax, Result}; +use crate::{FileSyntax, GraphSyntax, Result}; use failure::format_err; +use quick_xml::events::*; +use quick_xml::Writer; use std::fmt; use std::io::{BufRead, Write}; use uuid::Uuid; @@ -31,6 +33,97 @@ impl<'a> QueryResult<'a> { QueryResultSyntax::Json => write_json_results(self, writer), } } + + pub fn write_graph(self, mut writer: W, syntax: GraphSyntax) -> Result { + if let QueryResult::Graph(triples) = self { + match syntax { + GraphSyntax::NTriples | GraphSyntax::Turtle => { + for triple in triples { + writeln!(&mut writer, "{}", triple?)? + } + Ok(writer) + } + GraphSyntax::RdfXml => { + let mut writer = Writer::new(writer); + writer.write_event(Event::Decl(BytesDecl::new(b"1.0", None, None)))?; + let mut rdf_open = BytesStart::borrowed_name(b"rdf:RDF"); + rdf_open.push_attribute(( + "xmlns:rdf", + "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + )); + writer.write_event(Event::Start(rdf_open))?; + + let mut current_subject = None; + for triple in triples { + let triple = triple?; + + // We open a new rdf:Description if useful + if current_subject.as_ref() != Some(triple.subject()) { + if current_subject.is_some() { + writer.write_event(Event::End(BytesEnd::borrowed( + b"rdf:Description", + )))?; + } + + let mut description_open = + BytesStart::borrowed_name(b"rdf:Description"); + match triple.subject() { + NamedOrBlankNode::NamedNode(n) => { + description_open.push_attribute(("rdf:about", n.as_str())) + } + NamedOrBlankNode::BlankNode(n) => { + let id = n.as_uuid().to_simple().to_string(); + description_open.push_attribute(("rdf:nodeID", id.as_str())) + } + } + writer.write_event(Event::Start(description_open))?; + } + + let mut property_open = BytesStart::borrowed_name(b"prop:"); + let mut content = None; + property_open.push_attribute(("xmlns:prop", triple.predicate().as_str())); + match triple.object() { + Term::NamedNode(n) => { + property_open.push_attribute(("rdf:resource", n.as_str())) + } + Term::BlankNode(n) => { + let id = n.as_uuid().to_simple().to_string(); + property_open.push_attribute(("rdf:nodeID", id.as_str())) + } + Term::Literal(l) => { + if let Some(language) = l.language() { + property_open.push_attribute(("xml:lang", language.as_str())) + } else if !l.is_plain() { + property_open + .push_attribute(("rdf:datatype", l.datatype().as_str())) + } + content = Some(l.value()); + } + } + if let Some(content) = content { + writer.write_event(Event::Start(property_open))?; + writer.write_event(Event::Text(BytesText::from_plain_str(&content)))?; + writer.write_event(Event::End(BytesEnd::borrowed(b"prop:")))?; + } else { + writer.write_event(Event::Empty(property_open))?; + } + + current_subject = Some(triple.subject_owned()); + } + + if current_subject.is_some() { + writer.write_event(Event::End(BytesEnd::borrowed(b"rdf:Description")))?; + } + writer.write_event(Event::End(BytesEnd::borrowed(b"rdf:RDF")))?; + Ok(writer.into_inner()) + } + } + } else { + Err(format_err!( + "Bindings or booleans could not be formatted as an RDF graph" + )) + } + } } /// [SPARQL query](https://www.w3.org/TR/sparql11-query/) serialization formats diff --git a/lib/src/sparql/xml_results.rs b/lib/src/sparql/xml_results.rs index 84d4f777..939f44e9 100644 --- a/lib/src/sparql/xml_results.rs +++ b/lib/src/sparql/xml_results.rs @@ -132,7 +132,7 @@ pub fn read_xml_results<'a>(source: impl BufRead + 'a) -> Result if ns != b"http://www.w3.org/2005/sparql-results#".as_ref() { return Err(format_err!( "Unexpected namespace found in RDF/XML query result: {}", - reader.decode(ns) + reader.decode(ns)? )); } } @@ -144,20 +144,20 @@ pub fn read_xml_results<'a>(source: impl BufRead + 'a) -> Result if event.name() == b"sparql" { state = State::Sparql; } else { - return Err(format_err!("Expecting tag, found {}", reader.decode(event.name()))); + return Err(format_err!("Expecting tag, found {}", reader.decode(event.name())?)); } } State::Sparql => { if event.name() == b"head" { state = State::Head; } else { - return Err(format_err!("Expecting tag, found {}", reader.decode(event.name()))); + return Err(format_err!("Expecting tag, found {}", reader.decode(event.name())?)); } } State::Head => if event.name() == b"variable" || event.name() == b"link" { return Err(format_err!(" and tag should be autoclosing")); } else { - return Err(format_err!("Expecting or tag, found {}", reader.decode(event.name()))); + return Err(format_err!("Expecting or tag, found {}", reader.decode(event.name())?)); } State::AfterHead => { if event.name() == b"boolean" { @@ -178,10 +178,10 @@ pub fn read_xml_results<'a>(source: impl BufRead + 'a) -> Result }), ))); } else if event.name() != b"link" && event.name() != b"results" && event.name() != b"boolean" { - return Err(format_err!("Expecting sparql tag, found {}", reader.decode(event.name()))); + return Err(format_err!("Expecting sparql tag, found {}", reader.decode(event.name())?)); } } - State::Boolean => return Err(format_err!("Unexpected tag inside of tag: {}", reader.decode(event.name()))) + State::Boolean => return Err(format_err!("Unexpected tag inside of tag: {}", reader.decode(event.name())?)) }, Event::Empty(event) => match state { State::Head => { @@ -194,7 +194,7 @@ pub fn read_xml_results<'a>(source: impl BufRead + 'a) -> Result } else if event.name() == b"link" { // no op } else { - return Err(format_err!("Expecting or tag, found {}", reader.decode(event.name()))); + return Err(format_err!("Expecting or tag, found {}", reader.decode(event.name())?)); } }, State::AfterHead => { @@ -204,10 +204,10 @@ pub fn read_xml_results<'a>(source: impl BufRead + 'a) -> Result Box::new(empty()), ))) } else { - return Err(format_err!("Unexpected autoclosing tag <{}>", reader.decode(event.name()))) + return Err(format_err!("Unexpected autoclosing tag <{}>", reader.decode(event.name())?)) } } - _ => return Err(format_err!("Unexpected autoclosing tag <{}>", reader.decode(event.name()))) + _ => return Err(format_err!("Unexpected autoclosing tag <{}>", reader.decode(event.name())?)) }, Event::Text(event) => { let value = event.unescaped()?; @@ -218,10 +218,10 @@ pub fn read_xml_results<'a>(source: impl BufRead + 'a) -> Result } else if value.as_ref() == b"false" { Ok(QueryResult::Boolean(false)) } else { - Err(format_err!("Unexpected boolean value. Found {}", reader.decode(&value))) + Err(format_err!("Unexpected boolean value. Found {}", reader.decode(&value)?)) }; } - _ => Err(format_err!("Unexpected textual value found: {}", reader.decode(&value))) + _ => Err(format_err!("Unexpected textual value found: {}", reader.decode(&value)?)) }; }, Event::End(_) => if let State::Head = state { @@ -247,6 +247,12 @@ impl Iterator for ResultsIterator { type Item = Result>>; fn next(&mut self) -> Option>>> { + self.read_next().transpose() + } +} + +impl ResultsIterator { + fn read_next(&mut self) -> Result>>> { enum State { Start, Result, @@ -266,19 +272,15 @@ impl Iterator for ResultsIterator { let mut lang = None; let mut datatype = None; loop { - let (ns, event) = match self + let (ns, event) = self .reader - .read_namespaced_event(&mut self.buffer, &mut self.namespace_buffer) - { - Ok(v) => v, - Err(error) => return Some(Err(error.into())), - }; + .read_namespaced_event(&mut self.buffer, &mut self.namespace_buffer)?; if let Some(ns) = ns { if ns != b"http://www.w3.org/2005/sparql-results#".as_ref() { - return Some(Err(format_err!( + return Err(format_err!( "Unexpected namespace found in RDF/XML query result: {}", - self.reader.decode(ns) - ))); + self.reader.decode(ns)? + )); } } match event { @@ -287,10 +289,10 @@ impl Iterator for ResultsIterator { if event.name() == b"result" { state = State::Result; } else { - return Some(Err(format_err!( + return Err(format_err!( "Expecting , found {}", - self.reader.decode(event.name()) - ))); + self.reader.decode(event.name())? + )); } } State::Result => { @@ -300,29 +302,26 @@ impl Iterator for ResultsIterator { .filter_map(|attr| attr.ok()) .find(|attr| attr.key == b"name") { - Some(attr) => match attr.unescaped_value() { - Ok(var) => current_var = Some(var.to_vec()), - Err(error) => return Some(Err(error.into())), - }, + Some(attr) => current_var = Some(attr.unescaped_value()?.to_vec()), None => { - return Some(Err(format_err!( + return Err(format_err!( "No name attribute found for the tag" - ))); + )); } } state = State::Binding; } else { - return Some(Err(format_err!( + return Err(format_err!( "Expecting , found {}", - self.reader.decode(event.name()) - ))); + self.reader.decode(event.name())? + )); } } State::Binding => { if term.is_some() { - return Some(Err(format_err!( + return Err(format_err!( "There is already a value for the current binding" - ))); + )); } if event.name() == b"uri" { state = State::Uri; @@ -332,43 +331,30 @@ impl Iterator for ResultsIterator { for attr in event.attributes() { if let Ok(attr) = attr { if attr.key == b"xml:lang" { - match attr.unescape_and_decode_value(&self.reader) { - Ok(val) => lang = Some(val), - Err(error) => return Some(Err(error.into())), - } + lang = Some(attr.unescape_and_decode_value(&self.reader)?); } else if attr.key == b"datatype" { - match attr.unescaped_value() { - Ok(val) => { - match NamedNode::parse( - self.reader.decode(&val).to_string(), - ) { - Ok(iri) => datatype = Some(iri), - Err(error) => return Some(Err(error)), - } - } - Err(error) => return Some(Err(error.into())), - } + datatype = Some(NamedNode::parse( + attr.unescape_and_decode_value(&self.reader)?, + )?); } } } state = State::Literal; } else { - return Some(Err(format_err!( + return Err(format_err!( "Expecting , or found {}", - self.reader.decode(event.name()) - ))); + self.reader.decode(event.name())? + )); } } _ => (), }, - Event::Text(event) => match event.unescaped() { - Ok(data) => match state { - State::Uri => match NamedNode::parse(self.reader.decode(&data)) { - Ok(uri) => { - term = Some(uri.into()); - } - Err(error) => return Some(Err(error)), - }, + Event::Text(event) => { + let data = event.unescaped()?; + match state { + State::Uri => { + term = Some(NamedNode::parse(self.reader.decode(&data)?)?.into()) + } State::BNode => { term = Some( self.bnodes_map @@ -379,33 +365,33 @@ impl Iterator for ResultsIterator { ) } State::Literal => { - let value = self.reader.decode(&data).to_string(); - term = Some(build_literal(value, &lang, &datatype).into()); + term = Some( + build_literal(self.reader.decode(&data)?, &lang, &datatype).into(), + ); } _ => { - return Some(Err(format_err!( + return Err(format_err!( "Unexpected textual value found: {}", - self.reader.decode(&data) - ))); + self.reader.decode(&data)? + )); } - }, - Err(error) => return Some(Err(error.into())), - }, + } + } Event::End(_) => match state { State::Start => state = State::End, - State::Result => return Some(Ok(new_bindings)), + State::Result => return Ok(Some(new_bindings)), State::Binding => { match (¤t_var, &term) { (Some(var), Some(term)) => { new_bindings[self.mapping[var]] = Some(term.clone()) } (Some(var), None) => { - return Some(Err(format_err!( + return Err(format_err!( "No variable found for variable {}", - self.reader.decode(&var) - ))); + self.reader.decode(&var)? + )); } - _ => return Some(Err(format_err!("No name found for tag"))), + _ => return Err(format_err!("No name found for tag")), } term = None; state = State::Result; @@ -420,7 +406,7 @@ impl Iterator for ResultsIterator { } _ => (), }, - Event::Eof => return None, + Event::Eof => return Ok(None), _ => (), } } diff --git a/server/Cargo.toml b/server/Cargo.toml index d3d2661e..63d95c93 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -11,6 +11,6 @@ Rudf based SPARQL server edition = "2018" [dependencies] -rudf = {path = "../lib", features=["rocksdb"]} +rudf = {path = "../lib", features = ["rocksdb"] } clap = "2" rouille = "3" \ No newline at end of file diff --git a/server/src/main.rs b/server/src/main.rs index 6cd6a791..d198932c 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -10,7 +10,6 @@ use rudf::{ DatasetSyntax, FileSyntax, GraphSyntax, MemoryRepository, Repository, RepositoryConnection, RocksDbRepository, }; -use std::fmt::Write; use std::io::{BufReader, Read}; use std::sync::Arc; @@ -143,11 +142,13 @@ fn evaluate_sparql_query( ) -> Response { //TODO: stream match connection.prepare_query(query, None) { - Ok(query) => match query.exec().unwrap() { - QueryResult::Graph(triples) => { + Ok(query) => { + let results = query.exec().unwrap(); + if let QueryResult::Graph(_) = results { let supported_formats = [ GraphSyntax::NTriples.media_type(), GraphSyntax::Turtle.media_type(), + GraphSyntax::RdfXml.media_type(), ]; let format = if let Some(accept) = request.header("Accept") { if let Some(media_type) = @@ -165,13 +166,12 @@ fn evaluate_sparql_query( } else { GraphSyntax::NTriples }; - let mut result = String::default(); - for triple in triples { - writeln!(&mut result, "{}", triple.unwrap()).unwrap() - } - Response::from_data(format.media_type(), result.into_bytes()) - } - result => { + + Response::from_data( + format.media_type(), + results.write_graph(Vec::default(), format).unwrap(), + ) + } else { let supported_formats = [ QueryResultSyntax::Xml.media_type(), QueryResultSyntax::Json.media_type(), @@ -190,15 +190,15 @@ fn evaluate_sparql_query( .with_status_code(415); } } else { - QueryResultSyntax::Xml + QueryResultSyntax::Json }; Response::from_data( format.media_type(), - result.write(Vec::default(), format).unwrap(), + results.write(Vec::default(), format).unwrap(), ) } - }, + } Err(error) => Response::text(error.to_string()).with_status_code(400), } }