use oxrdf::Graph;
use oxttl::TurtleParseError;
use sparesults::{
QueryResultsFormat, QueryResultsParseError, QueryResultsParser, QuerySolution,
ReaderQueryResultsParserOutput,
};
use url::Url;
use crate::utils::parse_ttl_into_graph;
#[derive(Debug, thiserror::Error)]
pub enum SelectError {
#[error("HTTP error: {0}")]
Http(#[from] reqwest::Error),
#[error("Parse error: {0}")]
Parse(#[from] QueryResultsParseError),
#[error("Unexpected query results format. Expected query solutions, got boolean.")]
UnexpectedFormat,
}
#[derive(Debug, thiserror::Error)]
pub enum AskError {
#[error("HTTP error: {0}")]
Http(#[from] reqwest::Error),
#[error("Parse error: {0}")]
Parse(#[from] QueryResultsParseError),
#[error("Unexpected query results format. Expected query solutions, got boolean.")]
UnexpectedFormat,
}
#[derive(Debug, thiserror::Error)]
pub enum SelectGraphError {
#[error("HTTP error: {0}")]
Http(#[from] reqwest::Error),
#[error("Parse error: {0}")]
Parse(#[from] TurtleParseError),
}
#[derive(Debug, thiserror::Error)]
pub enum UpdateError {
#[error("HTTP error: {0}")]
Http(#[from] reqwest::Error),
#[error("SPARQL update failed with status: {0}")]
FailedToUpdate(reqwest::StatusCode),
}
pub struct SparqlClient {
http_client: reqwest::Client,
service_url: Url,
}
impl SparqlClient {
pub fn new(service_url: Url) -> Self {
Self {
http_client: reqwest::Client::new(),
service_url,
}
}
pub fn with_http_client(service_url: Url, http_client: reqwest::Client) -> Self {
Self {
http_client,
service_url,
}
}
fn build_query_request(&self, query: &str) -> reqwest::RequestBuilder {
self.http_client
.post(self.service_url.clone())
.header("Content-Type", "application/sparql-query")
.body(query.to_owned())
}
pub async fn select(&self, query: &str) -> Result<Vec<QuerySolution>, SelectError> {
let response = self
.build_query_request(query)
.header("Accept", "application/sparql-results+json")
.send()
.await?;
let body_bytes = response.bytes().await?;
let query_results_parser = QueryResultsParser::from_format(QueryResultsFormat::Json);
let ReaderQueryResultsParserOutput::Solutions(solutions) =
query_results_parser.for_reader(&*body_bytes)?
else {
return Err(SelectError::UnexpectedFormat);
};
let solutions = solutions.into_iter().flatten().collect();
Ok(solutions)
}
pub async fn ask(&self, query: &str) -> Result<bool, AskError> {
let response = self
.build_query_request(query)
.header("Accept", "application/sparql-results+json")
.send()
.await?;
let body_bytes = response.bytes().await?;
let query_results_parser = QueryResultsParser::from_format(QueryResultsFormat::Json);
let ReaderQueryResultsParserOutput::Boolean(bool) =
query_results_parser.for_reader(&*body_bytes)?
else {
return Err(AskError::UnexpectedFormat);
};
Ok(bool)
}
pub async fn select_graph(&self, query: &str) -> Result<Graph, SelectGraphError> {
let response = self
.build_query_request(query)
.header("Accept", "text/turtle")
.send()
.await?;
let body = response.text().await?;
let graph = parse_ttl_into_graph(&body)?;
Ok(graph)
}
pub async fn update(&self, query: &str) -> Result<(), UpdateError> {
let response = self.build_query_request(query).send().await?;
if !response.status().is_success() {
return Err(UpdateError::FailedToUpdate(response.status()));
}
Ok(())
}
}