Adds JSON SPARQL results output

pull/10/head
Tpt 5 years ago
parent 06c0773e5a
commit 153eeb1033
  1. 123
      lib/src/sparql/json_results.rs
  2. 1
      lib/src/sparql/mod.rs
  3. 39
      lib/src/sparql/model.rs
  4. 77
      server/src/main.rs

@ -0,0 +1,123 @@
//! Implementation of [SPARQL Query Results XML Format](https://www.w3.org/TR/sparql11-results-json/)
use crate::model::*;
use crate::sparql::model::*;
use crate::Result;
use failure::format_err;
use std::io::Write;
pub fn write_json_results<W: Write>(results: QueryResult<'_>, mut sink: W) -> Result<W> {
match results {
QueryResult::Boolean(value) => {
sink.write_all(b"{\"head\":{},\"boolean\":")?;
sink.write_all(if value { b"true" } else { b"false" })?;
sink.write_all(b"}")?;
}
QueryResult::Bindings(bindings) => {
let (variables, results) = bindings.destruct();
sink.write_all(b"{\"head\":{\"vars\":[")?;
let mut start_vars = true;
for variable in &variables {
if start_vars {
start_vars = false;
} else {
sink.write_all(b",")?;
}
write_escaped_json_string(variable.name()?, &mut sink)?;
}
sink.write_all(b"]},\"results\":{\"bindings\":[")?;
let mut start_bindings = true;
for result in results {
if start_bindings {
start_bindings = false;
} else {
sink.write_all(b",")?;
}
sink.write_all(b"{")?;
let result = result?;
let mut start_binding = true;
for (i, value) in result.into_iter().enumerate() {
if let Some(term) = value {
if start_binding {
start_binding = false;
} else {
sink.write_all(b",")?;
}
write_escaped_json_string(variables[i].name()?, &mut sink)?;
match term {
Term::NamedNode(uri) => {
sink.write_all(b":{\"type\":\"uri\",\"value\":")?;
write_escaped_json_string(uri.as_str(), &mut sink)?;
sink.write_all(b"}")?;
}
Term::BlankNode(bnode) => {
sink.write_all(b":{\"type\":\"bnode\",\"value\":")?;
sink.write_fmt(format_args!("{}", bnode.as_uuid().to_simple()))?;
sink.write_all(b"}")?;
}
Term::Literal(literal) => {
sink.write_all(b":{\"type\":\"literal\",\"value\":")?;
write_escaped_json_string(&literal.value(), &mut sink)?;
if let Some(language) = literal.language() {
sink.write_all(b",\"xml:lang\":")?;
write_escaped_json_string(language, &mut sink)?;
} else if !literal.is_plain() {
sink.write_all(b",\"datatype\":")?;
write_escaped_json_string(
literal.datatype().as_str(),
&mut sink,
)?;
}
sink.write_all(b"}")?;
}
}
}
}
sink.write_all(b"}")?;
}
sink.write_all(b"]}}")?;
}
QueryResult::Graph(_) => {
return Err(format_err!(
"Graphs could not be formatted to SPARQL query results XML format"
));
}
}
Ok(sink)
}
fn write_escaped_json_string(s: &str, sink: &mut impl Write) -> Result<()> {
sink.write_all(b"\"")?;
for c in s.chars() {
match c {
'\\' => sink.write_all(b"\\\\"),
'"' => sink.write_all(b"\\\""),
c => {
if c < char::from(32) {
match c {
'\u{08}' => sink.write_all(b"\\b"),
'\u{0C}' => sink.write_all(b"\\f"),
'\n' => sink.write_all(b"\\n"),
'\r' => sink.write_all(b"\\r"),
'\t' => sink.write_all(b"\\t"),
c => {
let mut c = c as u8;
let mut result = [b'\\', b'u', 0, 0, 0, 0];
for i in (2..6).rev() {
let ch = c % 16;
result[i] = ch + if ch < 10 { b'0' } else { b'A' };
c /= 16;
}
sink.write_all(&result)
}
}
} else {
sink.write_fmt(format_args!("{}", c))
}
}
}?;
}
sink.write_all(b"\"")?;
Ok(())
}

@ -2,6 +2,7 @@
mod algebra; mod algebra;
mod eval; mod eval;
mod json_results;
mod model; mod model;
mod parser; mod parser;
mod plan; mod plan;

@ -1,6 +1,7 @@
use crate::model::*; use crate::model::*;
use crate::sparql::json_results::write_json_results;
use crate::sparql::xml_results::{read_xml_results, write_xml_results}; use crate::sparql::xml_results::{read_xml_results, write_xml_results};
use crate::Result; use crate::{FileSyntax, Result};
use failure::format_err; use failure::format_err;
use std::fmt; use std::fmt;
use std::io::{BufRead, Write}; use std::io::{BufRead, Write};
@ -20,12 +21,14 @@ impl<'a> QueryResult<'a> {
pub fn read(reader: impl BufRead + 'a, syntax: QueryResultSyntax) -> Result<Self> { pub fn read(reader: impl BufRead + 'a, syntax: QueryResultSyntax) -> Result<Self> {
match syntax { match syntax {
QueryResultSyntax::Xml => read_xml_results(reader), QueryResultSyntax::Xml => read_xml_results(reader),
QueryResultSyntax::Json => unimplemented!(),
} }
} }
pub fn write<W: Write>(self, writer: W, syntax: QueryResultSyntax) -> Result<W> { pub fn write<W: Write>(self, writer: W, syntax: QueryResultSyntax) -> Result<W> {
match syntax { match syntax {
QueryResultSyntax::Xml => write_xml_results(self, writer), QueryResultSyntax::Xml => write_xml_results(self, writer),
QueryResultSyntax::Json => write_json_results(self, writer),
} }
} }
} }
@ -35,6 +38,40 @@ impl<'a> QueryResult<'a> {
pub enum QueryResultSyntax { pub enum QueryResultSyntax {
/// [SPARQL Query Results XML Format](http://www.w3.org/TR/rdf-sparql-XMLres/) /// [SPARQL Query Results XML Format](http://www.w3.org/TR/rdf-sparql-XMLres/)
Xml, Xml,
/// [SPARQL Query Results JSON Format](https://www.w3.org/TR/sparql11-results-json/)
Json,
}
impl FileSyntax for QueryResultSyntax {
fn iri(self) -> &'static str {
unimplemented!()
}
fn media_type(self) -> &'static str {
match self {
QueryResultSyntax::Xml => "application/sparql-results+xml",
QueryResultSyntax::Json => "application/sparql-results+json",
}
}
fn file_extension(self) -> &'static str {
match self {
QueryResultSyntax::Xml => "srx",
QueryResultSyntax::Json => "srj",
}
}
fn from_mime_type(media_type: &str) -> Option<Self> {
if let Some(base_type) = media_type.split(';').next() {
match base_type {
"application/sparql-results+xml" => Some(QueryResultSyntax::Xml),
"application/sparql-results+json" => Some(QueryResultSyntax::Json),
_ => None,
}
} else {
None
}
}
} }
/// An iterator over results bindings /// An iterator over results bindings

@ -1,6 +1,7 @@
use clap::App; use clap::App;
use clap::Arg; use clap::Arg;
use clap::ArgMatches; use clap::ArgMatches;
use rouille::input::priority_header_preferred;
use rouille::url::form_urlencoded; use rouille::url::form_urlencoded;
use rouille::{start_server, Request, Response}; use rouille::{start_server, Request, Response};
use rudf::sparql::QueryResult; use rudf::sparql::QueryResult;
@ -89,20 +90,22 @@ fn handle_request<R: RepositoryConnection>(
Response::text("No content given").with_status_code(400) Response::text("No content given").with_status_code(400)
} }
} }
("/query", "GET") => { ("/query", "GET") => evaluate_urlencoded_sparql_query(
evaluate_urlencoded_sparql_query(connection, request.raw_query_string().as_bytes()) connection,
} request.raw_query_string().as_bytes(),
request,
),
("/query", "POST") => { ("/query", "POST") => {
if let Some(mut body) = request.data() { if let Some(mut body) = request.data() {
if let Some(content_type) = request.header("Content-Type") { if let Some(content_type) = request.header("Content-Type") {
if content_type.starts_with("application/sparql-query") { if content_type.starts_with("application/sparql-query") {
let mut buffer = String::default(); let mut buffer = String::default();
body.read_to_string(&mut buffer).unwrap(); body.read_to_string(&mut buffer).unwrap();
evaluate_sparql_query(connection, &buffer) evaluate_sparql_query(connection, &buffer, request)
} else if content_type.starts_with("application/x-www-form-urlencoded") { } else if content_type.starts_with("application/x-www-form-urlencoded") {
let mut buffer = Vec::default(); let mut buffer = Vec::default();
body.read_to_end(&mut buffer).unwrap(); body.read_to_end(&mut buffer).unwrap();
evaluate_urlencoded_sparql_query(connection, &buffer) evaluate_urlencoded_sparql_query(connection, &buffer, request)
} else { } else {
Response::text(format!( Response::text(format!(
"No supported content Content-Type given: {}", "No supported content Content-Type given: {}",
@ -124,31 +127,77 @@ fn handle_request<R: RepositoryConnection>(
fn evaluate_urlencoded_sparql_query<R: RepositoryConnection>( fn evaluate_urlencoded_sparql_query<R: RepositoryConnection>(
connection: R, connection: R,
encoded: &[u8], encoded: &[u8],
request: &Request,
) -> Response { ) -> Response {
if let Some((_, query)) = form_urlencoded::parse(encoded).find(|(k, _)| k == "query") { if let Some((_, query)) = form_urlencoded::parse(encoded).find(|(k, _)| k == "query") {
evaluate_sparql_query(connection, &query) evaluate_sparql_query(connection, &query, request)
} else { } else {
Response::text("You should set the 'query' parameter").with_status_code(400) Response::text("You should set the 'query' parameter").with_status_code(400)
} }
} }
fn evaluate_sparql_query<R: RepositoryConnection>(connection: R, query: &str) -> Response { fn evaluate_sparql_query<R: RepositoryConnection>(
connection: R,
query: &str,
request: &Request,
) -> Response {
//TODO: stream //TODO: stream
match connection.prepare_query(query, None) { match connection.prepare_query(query, None) {
Ok(query) => match query.exec().unwrap() { Ok(query) => match query.exec().unwrap() {
QueryResult::Graph(triples) => { QueryResult::Graph(triples) => {
let supported_formats = [
GraphSyntax::NTriples.media_type(),
GraphSyntax::Turtle.media_type(),
];
let format = if let Some(accept) = request.header("Accept") {
if let Some(media_type) =
priority_header_preferred(accept, supported_formats.iter().cloned())
.and_then(|p| GraphSyntax::from_mime_type(supported_formats[p]))
{
media_type
} else {
return Response::text(format!(
"No supported Accept given: {}. Supported format: {:?}",
accept, supported_formats
))
.with_status_code(415);
}
} else {
GraphSyntax::NTriples
};
let mut result = String::default(); let mut result = String::default();
for triple in triples { for triple in triples {
writeln!(&mut result, "{}", triple.unwrap()).unwrap() writeln!(&mut result, "{}", triple.unwrap()).unwrap()
} }
Response::from_data(GraphSyntax::NTriples.media_type(), result.into_bytes()) Response::from_data(format.media_type(), result.into_bytes())
}
result => {
let supported_formats = [
QueryResultSyntax::Xml.media_type(),
QueryResultSyntax::Json.media_type(),
];
let format = if let Some(accept) = request.header("Accept") {
if let Some(media_type) =
priority_header_preferred(accept, supported_formats.iter().cloned())
.and_then(|p| QueryResultSyntax::from_mime_type(supported_formats[p]))
{
media_type
} else {
return Response::text(format!(
"No supported Accept given: {}. Supported format: {:?}",
accept, supported_formats
))
.with_status_code(415);
}
} else {
QueryResultSyntax::Xml
};
Response::from_data(
format.media_type(),
result.write(Vec::default(), format).unwrap(),
)
} }
result => Response::from_data(
"application/sparql-results",
result
.write(Vec::default(), QueryResultSyntax::Xml)
.unwrap(),
),
}, },
Err(error) => Response::text(error.to_string()).with_status_code(400), Err(error) => Response::text(error.to_string()).with_status_code(400),
} }

Loading…
Cancel
Save