srdf/srdf_sparql/
srdfsparql.rs

1use crate::matcher::{Any, Matcher};
2use crate::SRDFSparqlError;
3use crate::{AsyncSRDF, Query, QuerySolution, QuerySolutions, Rdf, Sparql, VarName};
4use async_trait::async_trait;
5use colored::*;
6use iri_s::IriS;
7use oxrdf::{
8    BlankNode as OxBlankNode, Literal as OxLiteral, NamedNode as OxNamedNode, Subject as OxSubject,
9    Term as OxTerm, Triple as OxTriple,
10};
11use prefixmap::PrefixMap;
12use regex::Regex;
13use sparesults::QuerySolution as OxQuerySolution;
14use std::{collections::HashSet, fmt::Display, str::FromStr};
15
16#[cfg(target_family = "wasm")]
17#[derive(Debug, Clone)]
18struct Client();
19
20#[cfg(not(target_family = "wasm"))]
21pub use reqwest::blocking::Client;
22
23type Result<A> = std::result::Result<A, SRDFSparqlError>;
24
25/// Implements SRDF interface as a SPARQL endpoint
26#[derive(Debug, Clone)]
27pub struct SRDFSparql {
28    endpoint_iri: IriS,
29    prefixmap: PrefixMap,
30    client: Client,
31}
32
33impl SRDFSparql {
34    pub fn new(iri: &IriS, prefixmap: &PrefixMap) -> Result<SRDFSparql> {
35        let client = sparql_client()?;
36        Ok(SRDFSparql {
37            endpoint_iri: iri.clone(),
38            prefixmap: prefixmap.clone(),
39            client,
40        })
41    }
42
43    pub fn iri(&self) -> &IriS {
44        &self.endpoint_iri
45    }
46
47    pub fn prefixmap(&self) -> &PrefixMap {
48        &self.prefixmap
49    }
50
51    pub fn wikidata() -> Result<SRDFSparql> {
52        let endpoint = SRDFSparql::new(
53            &IriS::new_unchecked("https://query.wikidata.org/sparql"),
54            &PrefixMap::wikidata(),
55        )?;
56        Ok(endpoint)
57    }
58
59    pub fn with_prefixmap(mut self, pm: PrefixMap) -> SRDFSparql {
60        self.prefixmap = pm;
61        self
62    }
63
64    fn show_blanknode(&self, bn: &OxBlankNode) -> String {
65        let str: String = format!("{}", bn);
66        format!("{}", str.green())
67    }
68
69    pub fn show_literal(&self, lit: &OxLiteral) -> String {
70        let str: String = format!("{}", lit);
71        format!("{}", str.red())
72    }
73}
74
75impl FromStr for SRDFSparql {
76    type Err = SRDFSparqlError;
77
78    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
79        let re_iri = Regex::new(r"<(.*)>").unwrap();
80        if let Some(iri_str) = re_iri.captures(s) {
81            let iri_s = IriS::from_str(&iri_str[1])?;
82            let client = sparql_client()?;
83            Ok(SRDFSparql {
84                endpoint_iri: iri_s,
85                prefixmap: PrefixMap::new(),
86                client,
87            })
88        } else {
89            match s.to_lowercase().as_str() {
90                "wikidata" => SRDFSparql::wikidata(),
91                name => Err(SRDFSparqlError::UnknownEndpontName {
92                    name: name.to_string(),
93                }),
94            }
95        }
96    }
97}
98
99impl Rdf for SRDFSparql {
100    type IRI = OxNamedNode;
101    type BNode = OxBlankNode;
102    type Literal = OxLiteral;
103    type Subject = OxSubject;
104    type Term = OxTerm;
105    type Triple = OxTriple;
106    type Err = SRDFSparqlError;
107
108    fn resolve_prefix_local(
109        &self,
110        prefix: &str,
111        local: &str,
112    ) -> std::result::Result<IriS, prefixmap::PrefixMapError> {
113        self.prefixmap.resolve_prefix_local(prefix, local)
114    }
115
116    fn qualify_iri(&self, node: &OxNamedNode) -> String {
117        let iri = IriS::from_str(node.as_str()).unwrap();
118        self.prefixmap.qualify(&iri)
119    }
120
121    fn qualify_subject(&self, subj: &OxSubject) -> String {
122        match subj {
123            OxSubject::BlankNode(bn) => self.show_blanknode(bn),
124            OxSubject::NamedNode(n) => self.qualify_iri(n),
125            #[cfg(feature = "rdf-star")]
126            OxSubject::Triple(_) => unimplemented!(),
127        }
128    }
129
130    fn qualify_term(&self, term: &OxTerm) -> String {
131        match term {
132            OxTerm::BlankNode(bn) => self.show_blanknode(bn),
133            OxTerm::Literal(lit) => self.show_literal(lit),
134            OxTerm::NamedNode(n) => self.qualify_iri(n),
135            #[cfg(feature = "rdf-star")]
136            OxTerm::Triple(_) => unimplemented!(),
137        }
138    }
139
140    fn prefixmap(&self) -> Option<PrefixMap> {
141        Some(self.prefixmap.clone())
142    }
143}
144
145#[async_trait]
146impl AsyncSRDF for SRDFSparql {
147    type IRI = OxNamedNode;
148    type BNode = OxBlankNode;
149    type Literal = OxLiteral;
150    type Subject = OxSubject;
151    type Term = OxTerm;
152    type Err = SRDFSparqlError;
153
154    async fn get_predicates_subject(&self, subject: &OxSubject) -> Result<HashSet<OxNamedNode>> {
155        let query = format!(r#"select ?pred where {{ {} ?pred ?obj . }}"#, subject);
156        let solutions = make_sparql_query(query.as_str(), &self.client, &self.endpoint_iri)?;
157        let mut results = HashSet::new();
158        for solution in solutions {
159            let n = get_iri_solution(solution, "pred")?;
160            results.insert(n.clone());
161        }
162        Ok(results)
163    }
164
165    async fn get_objects_for_subject_predicate(
166        &self,
167        _subject: &OxSubject,
168        _pred: &OxNamedNode,
169    ) -> Result<HashSet<OxTerm>> {
170        todo!();
171    }
172
173    async fn get_subjects_for_object_predicate(
174        &self,
175        _object: &OxTerm,
176        _pred: &OxNamedNode,
177    ) -> Result<HashSet<OxSubject>> {
178        todo!();
179    }
180}
181
182impl Query for SRDFSparql {
183    fn triples(&self) -> Result<impl Iterator<Item = Self::Triple>> {
184        self.triples_matching(Any, Any, Any)
185    }
186
187    fn triples_matching<S, P, O>(
188        &self,
189        subject: S,
190        predicate: P,
191        object: O,
192    ) -> Result<impl Iterator<Item = Self::Triple>>
193    where
194        S: Matcher<Self::Subject>,
195        P: Matcher<Self::IRI>,
196        O: Matcher<Self::Term>,
197    {
198        let query = format!(
199            "SELECT ?s ?p ?o WHERE {{ {} {} {} }}",
200            match subject.value() {
201                Some(s) => s.to_string(),
202                None => "?s".to_string(),
203            },
204            match predicate.value() {
205                Some(p) => p.to_string(),
206                None => "?p".to_string(),
207            },
208            match object.value() {
209                Some(o) => o.to_string(),
210                None => "?o".to_string(),
211            },
212        );
213
214        let triples = self
215            .query_select(&query)? // TODO: check this unwrap
216            .into_iter()
217            .map(move |solution| {
218                let subject: Self::Subject = match subject.value() {
219                    Some(s) => s,
220                    None => solution
221                        .find_solution(0)
222                        .and_then(|s| s.clone().try_into().ok())
223                        .unwrap(), // we know that this won't panic
224                };
225
226                let predicate: Self::IRI = match predicate.value() {
227                    Some(p) => p,
228                    None => solution
229                        .find_solution(1)
230                        .and_then(|pred| pred.clone().try_into().ok())
231                        .unwrap(), // we know that this won't panic
232                };
233
234                let object = match object.value() {
235                    Some(o) => o,
236                    None => solution.find_solution(2).cloned().unwrap(), // we know that this won't panic
237                };
238
239                OxTriple::new(subject, predicate, object)
240            });
241
242        Ok(triples)
243    }
244}
245
246impl Sparql for SRDFSparql {
247    fn query_select(&self, query: &str) -> Result<QuerySolutions<Self>> {
248        let solutions = make_sparql_query(query, &self.client, &self.endpoint_iri)?;
249        let qs: Vec<QuerySolution<SRDFSparql>> = solutions.iter().map(cnv_query_solution).collect();
250        Ok(QuerySolutions::new(qs))
251    }
252
253    fn query_ask(&self, query: &str) -> Result<bool> {
254        make_sparql_query(query, &self.client, &self.endpoint_iri)?
255            .first()
256            .and_then(|query_solution| query_solution.get(0))
257            .and_then(|term| match term {
258                OxTerm::Literal(literal) => Some(literal.value()),
259                _ => None,
260            })
261            .and_then(|value| value.parse().ok())
262            .ok_or_else(|| todo!())
263    }
264}
265
266fn cnv_query_solution(qs: &OxQuerySolution) -> QuerySolution<SRDFSparql> {
267    let mut variables = Vec::new();
268    let mut values = Vec::new();
269    for v in qs.variables() {
270        let varname = VarName::new(v.as_str());
271        variables.push(varname);
272    }
273    for t in qs.values() {
274        let term = t.clone();
275        values.push(term)
276    }
277    QuerySolution::new(variables, values)
278}
279
280#[cfg(target_family = "wasm")]
281fn sparql_client() -> Result<Client> {
282    Ok(Client())
283}
284
285#[cfg(not(target_family = "wasm"))]
286fn sparql_client() -> Result<Client> {
287    use reqwest::header::{self, ACCEPT, USER_AGENT};
288
289    let mut headers = header::HeaderMap::new();
290    headers.insert(
291        ACCEPT,
292        header::HeaderValue::from_static("application/sparql-results+json"),
293    );
294    headers.insert(USER_AGENT, header::HeaderValue::from_static("rudof"));
295    let client = reqwest::blocking::Client::builder()
296        .default_headers(headers)
297        .build()?;
298    Ok(client)
299}
300
301#[cfg(target_family = "wasm")]
302fn make_sparql_query(
303    _query: &str,
304    _client: &Client,
305    _endpoint_iri: &IriS,
306) -> Result<Vec<OxQuerySolution>> {
307    Err(SRDFSparqlError::UnknownEndpontName {
308        name: String::from("WASM"),
309    })
310}
311
312#[cfg(not(target_family = "wasm"))]
313fn make_sparql_query(
314    query: &str,
315    client: &Client,
316    endpoint_iri: &IriS,
317) -> Result<Vec<OxQuerySolution>> {
318    use sparesults::{QueryResultsFormat, QueryResultsParser, ReaderQueryResultsParserOutput};
319    use url::Url;
320
321    let url = Url::parse_with_params(endpoint_iri.as_str(), &[("query", query)])?;
322    tracing::debug!("SPARQL query: {}", url);
323    let body = client.get(url).send()?.text()?;
324    let mut results = Vec::new();
325    let json_parser = QueryResultsParser::from_format(QueryResultsFormat::Json);
326    if let ReaderQueryResultsParserOutput::Solutions(solutions) =
327        json_parser.for_reader(body.as_bytes())?
328    {
329        for solution in solutions {
330            let sol = solution?;
331            results.push(sol)
332        }
333        Ok(results)
334    } else {
335        Err(SRDFSparqlError::ParsingBody { body })
336    }
337}
338
339#[derive(Debug)]
340pub struct SparqlVars {
341    values: Vec<String>,
342}
343
344impl Display for SparqlVars {
345    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
346        f.write_str(self.values.join(", ").as_str())
347    }
348}
349
350fn get_iri_solution(solution: OxQuerySolution, name: &str) -> Result<OxNamedNode> {
351    match solution.get(name) {
352        Some(v) => match v {
353            OxTerm::NamedNode(n) => Ok(n.clone()),
354            _ => Err(SRDFSparqlError::SPARQLSolutionErrorNoIRI { value: v.clone() }),
355        },
356        None => Err(SRDFSparqlError::NotFoundInSolution {
357            value: name.to_string(),
358            solution: format!("{solution:?}"),
359        }),
360    }
361}
362
363#[cfg(test)]
364mod tests {
365    use crate::Triple;
366
367    use super::*;
368    use oxrdf::{NamedNode, Subject};
369
370    #[test]
371    fn check_sparql() {
372        let wikidata = SRDFSparql::wikidata().unwrap();
373
374        let q80: Subject = NamedNode::new_unchecked("http://www.wikidata.org/entity/Q80").into();
375        let p19: NamedNode = NamedNode::new_unchecked("http://www.wikidata.org/prop/P19");
376
377        let data: Vec<_> = wikidata
378            .triples_with_subject(q80)
379            .unwrap()
380            .map(Triple::into_predicate)
381            .collect();
382
383        assert!(data.contains(&p19));
384    }
385}