oxigraph/sparql/
service.rs

1use crate::model::NamedNode;
2use crate::sparql::algebra::Query;
3use crate::sparql::error::EvaluationError;
4use crate::sparql::http::Client;
5use crate::sparql::model::QueryResults;
6use crate::sparql::results::QueryResultsFormat;
7use crate::sparql::QueryDataset;
8use oxiri::Iri;
9use sparesults::{QueryResultsParser, ReaderQueryResultsParserOutput};
10use spareval::{DefaultServiceHandler, QueryEvaluationError, QuerySolutionIter};
11use spargebra::algebra::GraphPattern;
12use std::error::Error;
13use std::time::Duration;
14
15/// Handler for [SPARQL 1.1 Federated Query](https://www.w3.org/TR/sparql11-federated-query/) SERVICE.
16///
17/// Should be given to [`QueryOptions`](super::QueryOptions::with_service_handler())
18/// before evaluating a SPARQL query that uses SERVICE calls.
19///
20/// ```
21/// use oxigraph::model::*;
22/// use oxigraph::sparql::{EvaluationError, Query, QueryOptions, QueryResults, ServiceHandler};
23/// use oxigraph::store::Store;
24///
25/// struct TestServiceHandler {
26///     store: Store,
27/// }
28///
29/// impl ServiceHandler for TestServiceHandler {
30///     type Error = EvaluationError;
31///
32///     fn handle(
33///         &self,
34///         service_name: NamedNode,
35///         query: Query,
36///     ) -> Result<QueryResults, Self::Error> {
37///         if service_name == "http://example.com/service" {
38///             self.store.query(query)
39///         } else {
40///             panic!()
41///         }
42///     }
43/// }
44///
45/// let store = Store::new()?;
46/// let service = TestServiceHandler {
47///     store: Store::new()?,
48/// };
49/// let ex = NamedNodeRef::new("http://example.com")?;
50/// service
51///     .store
52///     .insert(QuadRef::new(ex, ex, ex, GraphNameRef::DefaultGraph))?;
53///
54/// if let QueryResults::Solutions(mut solutions) = store.query_opt(
55///     "SELECT ?s WHERE { SERVICE <http://example.com/service> { ?s ?p ?o } }",
56///     QueryOptions::default().with_service_handler(service),
57/// )? {
58///     assert_eq!(solutions.next().unwrap()?.get("s"), Some(&ex.into()));
59/// }
60/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
61/// ```
62pub trait ServiceHandler: Send + Sync {
63    /// The service evaluation error.
64    type Error: Error + Send + Sync + 'static;
65
66    /// Evaluates a [`Query`] against a given service identified by a [`NamedNode`].
67    fn handle(&self, service_name: NamedNode, query: Query) -> Result<QueryResults, Self::Error>;
68}
69
70pub struct WrappedDefaultServiceHandler<H: ServiceHandler>(pub H);
71
72impl<H: ServiceHandler> DefaultServiceHandler for WrappedDefaultServiceHandler<H> {
73    type Error = QueryEvaluationError;
74
75    fn handle(
76        &self,
77        service_name: NamedNode,
78        pattern: GraphPattern,
79        base_iri: Option<String>,
80    ) -> Result<QuerySolutionIter, Self::Error> {
81        let QueryResults::Solutions(solutions) = self
82            .0
83            .handle(
84                service_name,
85                Query {
86                    inner: spargebra::Query::Select {
87                        dataset: None,
88                        pattern,
89                        base_iri: base_iri
90                            .map(Iri::parse)
91                            .transpose()
92                            .map_err(|e| QueryEvaluationError::Service(Box::new(e)))?,
93                    },
94                    dataset: QueryDataset::new(),
95                },
96            )
97            .map_err(|e| QueryEvaluationError::Service(Box::new(e)))?
98        else {
99            return Err(QueryEvaluationError::Service(
100                "Only query solutions are supported in services".into(),
101            ));
102        };
103        Ok(solutions.into())
104    }
105}
106
107pub struct EmptyServiceHandler;
108
109impl DefaultServiceHandler for EmptyServiceHandler {
110    type Error = QueryEvaluationError;
111
112    fn handle(
113        &self,
114        service_name: NamedNode,
115
116        _: GraphPattern,
117        _: Option<String>,
118    ) -> Result<QuerySolutionIter, QueryEvaluationError> {
119        Err(QueryEvaluationError::UnsupportedService(service_name))
120    }
121}
122
123pub struct SimpleServiceHandler {
124    client: Client,
125}
126
127impl SimpleServiceHandler {
128    pub fn new(http_timeout: Option<Duration>, http_redirection_limit: usize) -> Self {
129        Self {
130            client: Client::new(http_timeout, http_redirection_limit),
131        }
132    }
133}
134
135impl DefaultServiceHandler for SimpleServiceHandler {
136    type Error = EvaluationError;
137
138    fn handle(
139        &self,
140        service_name: NamedNode,
141        pattern: GraphPattern,
142        base_iri: Option<String>,
143    ) -> Result<QuerySolutionIter, Self::Error> {
144        let (content_type, body) = self
145            .client
146            .post(
147                service_name.as_str(),
148                spargebra::Query::Select {
149                    dataset: None,
150                    pattern,
151                    base_iri: base_iri
152                        .map(Iri::parse)
153                        .transpose()
154                        .map_err(|e| EvaluationError::Service(Box::new(e)))?,
155                }
156                .to_string()
157                .into_bytes(),
158                "application/sparql-query",
159                "application/sparql-results+json, application/sparql-results+xml",
160            )
161            .map_err(|e| EvaluationError::Service(Box::new(e)))?;
162        let format = QueryResultsFormat::from_media_type(&content_type)
163            .ok_or_else(|| EvaluationError::UnsupportedContentType(content_type))?;
164        let ReaderQueryResultsParserOutput::Solutions(reader) =
165            QueryResultsParser::from_format(format).for_reader(body)?
166        else {
167            return Err(EvaluationError::ServiceDoesNotReturnSolutions);
168        };
169        Ok(QuerySolutionIter::new(
170            reader.variables().into(),
171            Box::new(reader.map(|t| t.map_err(|e| QueryEvaluationError::Service(Box::new(e))))),
172        ))
173    }
174}