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#[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)? .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(), };
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(), };
233
234 let object = match object.value() {
235 Some(o) => o,
236 None => solution.find_solution(2).cloned().unwrap(), };
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}