Skip to main content

qualia_core_db/sparql_library/
sparql_endpoint.rs

1//! SPARQL HTTP Endpoint
2//!
3//! Provides HTTP endpoint for SPARQL queries (/sparql)
4
5use crate::sparql_ast::*;
6use crate::sparql_executor::*;
7use crate::sparql_library::serialisers::sparql_results::ResultFormatter;
8use crate::sparql_parser;
9use crate::sparql_planner::*;
10use crate::NQuin;
11
12/// SPARQL HTTP endpoint handler
13pub struct SparqlEndpoint<'a> {
14    quins: &'a [NQuin],
15}
16
17impl<'a> SparqlEndpoint<'a> {
18    pub fn new(quins: &'a [NQuin]) -> Self {
19        Self { quins }
20    }
21
22    /// Handle SPARQL query via HTTP
23    pub fn handle_query(&self, query: &str, format: &str) -> Result<String, String> {
24        // Parse query
25        let (sparql_query, ctx) = sparql_parser::parse_sparql(query)?;
26
27        // Plan query
28        let plan = QueryPlanner::plan(&sparql_query, &ctx)?;
29
30        // Execute query
31        let executor = QueryExecutor::new(&self.quins);
32        let results = executor.execute(&plan, &ctx)?;
33
34        // Format results
35        match format.to_lowercase().as_str() {
36            "xml" => {
37                let mut output = Vec::new();
38                let vars = match &sparql_query {
39                    SparqlQuery::Select(select) => {
40                        select.variables[..select.var_count as usize].to_vec()
41                    }
42                    _ => vec![],
43                };
44                ResultFormatter::format_xml(&mut output, &vars, &results, &ctx, None)
45                    .map_err(|e| e.to_string())?;
46                Ok(String::from_utf8(output).unwrap())
47            }
48            "json" => {
49                let mut output = Vec::new();
50                let vars = match &sparql_query {
51                    SparqlQuery::Select(select) => {
52                        select.variables[..select.var_count as usize].to_vec()
53                    }
54                    _ => vec![],
55                };
56                ResultFormatter::format_json(&mut output, &vars, &results, &ctx, None)
57                    .map_err(|e| e.to_string())?;
58                Ok(String::from_utf8(output).unwrap())
59            }
60            "tsv" => {
61                let mut output = Vec::new();
62                let vars = match &sparql_query {
63                    SparqlQuery::Select(select) => {
64                        select.variables[..select.var_count as usize].to_vec()
65                    }
66                    _ => vec![],
67                };
68                ResultFormatter::format_tsv(&mut output, &vars, &results, &ctx, None)
69                    .map_err(|e| e.to_string())?;
70                Ok(String::from_utf8(output).unwrap())
71            }
72            "csv" => {
73                let mut output = Vec::new();
74                let vars = match &sparql_query {
75                    SparqlQuery::Select(select) => {
76                        select.variables[..select.var_count as usize].to_vec()
77                    }
78                    _ => vec![],
79                };
80                ResultFormatter::format_csv(&mut output, &vars, &results, &ctx, None)
81                    .map_err(|e| e.to_string())?;
82                Ok(String::from_utf8(output).unwrap())
83            }
84            _ => Err("Unsupported format. Use: xml, json, tsv, or csv".to_string()),
85        }
86    }
87
88    /// Handle ASK query
89    pub fn handle_ask(&self, query: &str, format: &str) -> Result<String, String> {
90        let (sparql_query, ctx) = sparql_parser::parse_sparql(query)?;
91
92        let plan = QueryPlanner::plan(&sparql_query, &ctx)?;
93        let executor = QueryExecutor::new(&self.quins);
94        let has_results = executor.execute_ask(&plan, &ctx)?;
95
96        match format.to_lowercase().as_str() {
97            "xml" => {
98                let mut output = Vec::new();
99                ResultFormatter::format_ask_xml(&mut output, has_results)
100                    .map_err(|e| e.to_string())?;
101                Ok(String::from_utf8(output).unwrap())
102            }
103            "json" => {
104                let mut output = Vec::new();
105                ResultFormatter::format_ask_json(&mut output, has_results)
106                    .map_err(|e| e.to_string())?;
107                Ok(String::from_utf8(output).unwrap())
108            }
109            _ => Err("Unsupported format for ASK. Use: xml or json".to_string()),
110        }
111    }
112
113    /// Handle CONSTRUCT query
114    pub fn handle_construct(&self, query: &str, format: &str) -> Result<String, String> {
115        let (sparql_query, ctx) = sparql_parser::parse_sparql(query)?;
116
117        let template_pattern = match &sparql_query {
118            SparqlQuery::Construct(c) => c.template_pattern,
119            _ => return Err("Query is not a CONSTRUCT".to_string()),
120        };
121
122        let plan = QueryPlanner::plan(&sparql_query, &ctx)?;
123        let executor = QueryExecutor::new(&self.quins);
124        // Rows are the instantiated template triples (slots 0/1/2 = s/p/o).
125        let results = executor.execute_construct(&plan, &ctx, template_pattern)?;
126
127        match format.to_lowercase().as_str() {
128            "ntriples" => {
129                let mut output = Vec::new();
130                ResultFormatter::format_ntriples(&mut output, &results)
131                    .map_err(|e| e.to_string())?;
132                Ok(String::from_utf8(output).unwrap())
133            }
134            "xml" => {
135                let mut output = Vec::new();
136                // Use default variables for CONSTRUCT
137                let vars = vec![0u8, 1u8, 2u8]; // subject, predicate, object
138                ResultFormatter::format_xml(&mut output, &vars, &results, &ctx, None)
139                    .map_err(|e| e.to_string())?;
140                Ok(String::from_utf8(output).unwrap())
141            }
142            "json" => {
143                let mut output = Vec::new();
144                let vars = vec![0u8, 1u8, 2u8];
145                ResultFormatter::format_json(&mut output, &vars, &results, &ctx, None)
146                    .map_err(|e| e.to_string())?;
147                Ok(String::from_utf8(output).unwrap())
148            }
149            _ => Err("Unsupported format. Use: ntriples, xml, or json".to_string()),
150        }
151    }
152
153    /// Handle DESCRIBE query
154    pub fn handle_describe(&self, query: &str, format: &str) -> Result<String, String> {
155        let (sparql_query, ctx) = sparql_parser::parse_sparql(query)?;
156
157        let describe = match &sparql_query {
158            SparqlQuery::Describe(d) => *d,
159            _ => return Err("Query is not a DESCRIBE".to_string()),
160        };
161
162        let plan = QueryPlanner::plan(&sparql_query, &ctx)?;
163        let executor = QueryExecutor::new(&self.quins);
164        // Rows are the Concise Bounded Description triples (slots 0/1/2 = s/p/o).
165        let results = executor.execute_describe(&plan, &ctx, &describe)?;
166
167        match format.to_lowercase().as_str() {
168            "ntriples" => {
169                let mut output = Vec::new();
170                ResultFormatter::format_ntriples(&mut output, &results)
171                    .map_err(|e| e.to_string())?;
172                Ok(String::from_utf8(output).unwrap())
173            }
174            "xml" => {
175                let mut output = Vec::new();
176                let vars = match &sparql_query {
177                    SparqlQuery::Describe(describe) => {
178                        // Use first variable
179                        if describe.var_count > 0 {
180                            vec![describe.vars_or_ids[0] as VariableId]
181                        } else {
182                            vec![0]
183                        }
184                    }
185                    _ => vec![],
186                };
187                ResultFormatter::format_xml(&mut output, &vars, &results, &ctx, None)
188                    .map_err(|e| e.to_string())?;
189                Ok(String::from_utf8(output).unwrap())
190            }
191            "json" => {
192                let mut output = Vec::new();
193                let vars = match &sparql_query {
194                    SparqlQuery::Describe(describe) => {
195                        if describe.var_count > 0 {
196                            vec![describe.vars_or_ids[0] as VariableId]
197                        } else {
198                            vec![0]
199                        }
200                    }
201                    _ => vec![],
202                };
203                ResultFormatter::format_json(&mut output, &vars, &results, &ctx, None)
204                    .map_err(|e| e.to_string())?;
205                Ok(String::from_utf8(output).unwrap())
206            }
207            _ => Err("Unsupported format. Use: ntriples, xml, or json".to_string()),
208        }
209    }
210}
211
212/// SPARQL protocol handler
213pub struct SparqlProtocolHandler<'a> {
214    endpoint: SparqlEndpoint<'a>,
215}
216
217impl<'a> SparqlProtocolHandler<'a> {
218    pub fn new(quins: &'a [NQuin]) -> Self {
219        Self {
220            endpoint: SparqlEndpoint::new(quins),
221        }
222    }
223
224    /// Parse Content-Type header to determine format
225    pub fn parse_accept_header(accept: &str) -> String {
226        if accept.contains("application/sparql-results+xml") {
227            "xml".to_string()
228        } else if accept.contains("application/sparql-results+json") {
229            "json".to_string()
230        } else if accept.contains("text/tab-separated-values") {
231            "tsv".to_string()
232        } else if accept.contains("text/csv") {
233            "csv".to_string()
234        } else if accept.contains("application/n-triples") {
235            "ntriples".to_string()
236        } else if accept.contains("application/n-quads") {
237            "ntriples".to_string()
238        } else {
239            "json".to_string() // Default
240        }
241    }
242
243    /// Handle SPARQL protocol request
244    pub fn handle_request(
245        &self,
246        query: Option<&str>,
247        accept: Option<&str>,
248    ) -> Result<String, String> {
249        let query = query.ok_or("No query provided")?;
250        let format = accept
251            .map(|a| Self::parse_accept_header(a))
252            .unwrap_or_else(|| "json".to_string());
253
254        // Check query type and dispatch
255        let query_upper = query.trim().to_uppercase();
256
257        if query_upper.starts_with("ASK") {
258            self.endpoint.handle_ask(query, &format)
259        } else if query_upper.starts_with("CONSTRUCT") {
260            self.endpoint.handle_construct(query, &format)
261        } else if query_upper.starts_with("DESCRIBE") {
262            self.endpoint.handle_describe(query, &format)
263        } else {
264            // Default to SELECT
265            self.endpoint.handle_query(query, &format)
266        }
267    }
268}
269
270#[cfg(test)]
271mod tests {
272    use super::*;
273
274    #[test]
275    fn test_parse_accept_header() {
276        assert_eq!(
277            SparqlProtocolHandler::parse_accept_header("application/sparql-results+xml"),
278            "xml"
279        );
280        assert_eq!(
281            SparqlProtocolHandler::parse_accept_header("application/sparql-results+json"),
282            "json"
283        );
284        assert_eq!(
285            SparqlProtocolHandler::parse_accept_header("text/tab-separated-values"),
286            "tsv"
287        );
288        assert_eq!(
289            SparqlProtocolHandler::parse_accept_header("text/csv"),
290            "csv"
291        );
292        assert_eq!(
293            SparqlProtocolHandler::parse_accept_header("application/n-triples"),
294            "ntriples"
295        );
296        assert_eq!(
297            SparqlProtocolHandler::parse_accept_header("application/n-quads"),
298            "ntriples"
299        );
300    }
301}