oxihuman_export/
sparql_export.rs1#![allow(dead_code)]
4
5#[derive(Debug, Clone, PartialEq)]
9pub enum SparqlQueryType {
10 Select,
11 Construct,
12 Ask,
13 Describe,
14}
15
16impl SparqlQueryType {
17 pub fn keyword(&self) -> &'static str {
19 match self {
20 Self::Select => "SELECT",
21 Self::Construct => "CONSTRUCT",
22 Self::Ask => "ASK",
23 Self::Describe => "DESCRIBE",
24 }
25 }
26}
27
28#[derive(Debug, Clone)]
30pub struct SparqlPrefix {
31 pub prefix: String,
32 pub iri: String,
33}
34
35#[derive(Debug, Clone)]
37pub struct SparqlQuery {
38 pub query_type: SparqlQueryType,
39 pub prefixes: Vec<SparqlPrefix>,
40 pub variables: Vec<String>,
41 pub where_patterns: Vec<String>,
42 pub limit: Option<usize>,
43 pub offset: Option<usize>,
44}
45
46impl SparqlQuery {
47 pub fn select(variables: Vec<String>) -> Self {
49 Self {
50 query_type: SparqlQueryType::Select,
51 prefixes: Vec::new(),
52 variables,
53 where_patterns: Vec::new(),
54 limit: None,
55 offset: None,
56 }
57 }
58
59 pub fn add_prefix(&mut self, prefix: impl Into<String>, iri: impl Into<String>) {
61 self.prefixes.push(SparqlPrefix {
62 prefix: prefix.into(),
63 iri: iri.into(),
64 });
65 }
66
67 pub fn add_pattern(&mut self, pattern: impl Into<String>) {
69 self.where_patterns.push(pattern.into());
70 }
71
72 pub fn pattern_count(&self) -> usize {
74 self.where_patterns.len()
75 }
76}
77
78pub fn render_sparql(query: &SparqlQuery) -> String {
80 let mut out = String::new();
81 for p in &query.prefixes {
82 out.push_str(&format!("PREFIX {}: <{}>\n", p.prefix, p.iri));
83 }
84 let vars: Vec<String> = query.variables.iter().map(|v| format!("?{v}")).collect();
85 out.push_str(&format!(
86 "\n{} {}\n",
87 query.query_type.keyword(),
88 vars.join(" ")
89 ));
90 out.push_str("WHERE {\n");
91 for pat in &query.where_patterns {
92 out.push_str(&format!(" {pat}\n"));
93 }
94 out.push('}');
95 if let Some(limit) = query.limit {
96 out.push_str(&format!("\nLIMIT {limit}"));
97 }
98 if let Some(offset) = query.offset {
99 out.push_str(&format!("\nOFFSET {offset}"));
100 }
101 out.push('\n');
102 out
103}
104
105pub fn validate_query(query: &SparqlQuery) -> bool {
107 (query.query_type != SparqlQueryType::Select || !query.variables.is_empty())
108 && !query.where_patterns.is_empty()
109}
110
111pub fn add_schema_prefix(query: &mut SparqlQuery) {
113 query.add_prefix("schema", "https://schema.org/");
114}
115
116pub fn add_rdf_prefix(query: &mut SparqlQuery) {
118 query.add_prefix("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 fn sample_query() -> SparqlQuery {
126 let mut q = SparqlQuery::select(vec!["s".into(), "p".into(), "o".into()]);
127 q.add_prefix("schema", "https://schema.org/");
128 q.add_pattern("?s ?p ?o .");
129 q.limit = Some(10);
130 q
131 }
132
133 #[test]
134 fn pattern_count() {
135 assert_eq!(sample_query().pattern_count(), 1);
136 }
137
138 #[test]
139 fn render_contains_select() {
140 assert!(render_sparql(&sample_query()).contains("SELECT"));
141 }
142
143 #[test]
144 fn render_contains_where() {
145 assert!(render_sparql(&sample_query()).contains("WHERE"));
146 }
147
148 #[test]
149 fn render_contains_limit() {
150 assert!(render_sparql(&sample_query()).contains("LIMIT 10"));
151 }
152
153 #[test]
154 fn render_contains_prefix() {
155 assert!(render_sparql(&sample_query()).contains("PREFIX"));
156 }
157
158 #[test]
159 fn validate_ok() {
160 assert!(validate_query(&sample_query()));
161 }
162
163 #[test]
164 fn validate_no_pattern() {
165 let q = SparqlQuery::select(vec!["x".into()]);
166 assert!(!validate_query(&q));
167 }
168
169 #[test]
170 fn query_type_keyword() {
171 assert_eq!(SparqlQueryType::Ask.keyword(), "ASK");
172 }
173
174 #[test]
175 fn add_schema_prefix_adds_one() {
176 let mut q = SparqlQuery::select(vec![]);
177 add_schema_prefix(&mut q);
178 assert_eq!(q.prefixes.len(), 1);
179 }
180
181 #[test]
182 fn add_rdf_prefix_works() {
183 let mut q = SparqlQuery::select(vec![]);
184 add_rdf_prefix(&mut q);
185 assert!(q.prefixes[0].prefix == "rdf");
186 }
187}