1use 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
12use std::collections::HashMap;
13
14#[repr(C)]
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum WebSocketMessageType {
18 Query,
19 Subscribe,
20 Unsubscribe,
21 Result,
22 Error,
23 Close,
24}
25
26#[repr(C)]
28#[derive(Debug, Clone, Copy)]
29pub struct WebSocketMessage {
30 pub msg_type: WebSocketMessageType,
31 pub query_id: u64,
32 pub payload_len: u16,
33}
34
35#[repr(C)]
37#[derive(Debug, Clone, Copy)]
38pub struct WebSocketSession {
39 pub session_id: u64,
40 pub active_query: Option<u64>,
41 pub subscribed: bool,
42}
43
44#[derive(Debug, Clone)]
50pub struct Subscription {
51 pub subscription_id: String,
53 pub client_id: String,
55 pub query: String,
57}
58
59impl Subscription {
60 pub fn new(subscription_id: String, client_id: String, query: String) -> Self {
62 Self {
63 subscription_id,
64 client_id,
65 query,
66 }
67 }
68}
69
70pub struct SparqlWebSocketHandler<'a> {
72 pub quins: &'a [NQuin],
73 pub sessions: [Option<WebSocketSession>; 32],
74 pub session_count: u8,
75 pub subscriptions: HashMap<String, Subscription>,
77 pub initialized: bool,
79}
80
81impl<'a> SparqlWebSocketHandler<'a> {
82 pub fn new(quins: &'a [NQuin]) -> Self {
83 Self {
84 quins,
85 sessions: [None; 32],
86 session_count: 0,
87 subscriptions: HashMap::new(),
88 initialized: false,
89 }
90 }
91
92 pub fn initialize(&mut self) -> Result<(), String> {
96 self.subscriptions.clear();
97 self.initialized = true;
98 Ok(())
99 }
100
101 pub fn register_session(&mut self) -> Result<u64, String> {
103 if self.session_count >= 32 {
104 return Err("Session overflow".to_string());
105 }
106
107 let session_id = self.session_count as u64;
108 self.sessions[self.session_count as usize] = Some(WebSocketSession {
109 session_id,
110 active_query: None,
111 subscribed: false,
112 });
113 self.session_count += 1;
114
115 Ok(session_id)
116 }
117
118 pub fn unregister_session(&mut self, session_id: u64) -> Result<(), String> {
120 for i in 0..self.session_count as usize {
121 if let Some(session) = self.sessions[i] {
122 if session.session_id == session_id {
123 self.sessions[i] = None;
124 return Ok(());
125 }
126 }
127 }
128 Err("Session not found".to_string())
129 }
130
131 pub fn handle_query_formatted(
136 &self,
137 query: &str,
138 format: &str,
139 _session_id: u64,
140 ) -> Result<String, String> {
141 let (sparql_query, ctx) = sparql_parser::parse_sparql(query)?;
143
144 let plan = QueryPlanner::plan(&sparql_query, &ctx)?;
146
147 let executor = QueryExecutor::new(self.quins);
149 let results = executor.execute(&plan, &ctx)?;
150
151 match format.to_lowercase().as_str() {
153 "json" => {
154 let mut output = Vec::new();
155 let vars = match &sparql_query {
156 SparqlQuery::Select(select) => {
157 select.variables[..select.var_count as usize].to_vec()
158 }
159 _ => vec![],
160 };
161 ResultFormatter::format_json(&mut output, &vars, &results, &ctx, None)
162 .map_err(|e| e.to_string())?;
163 Ok(String::from_utf8(output).unwrap())
164 }
165 "xml" => {
166 let mut output = Vec::new();
167 let vars = match &sparql_query {
168 SparqlQuery::Select(select) => {
169 select.variables[..select.var_count as usize].to_vec()
170 }
171 _ => vec![],
172 };
173 ResultFormatter::format_xml(&mut output, &vars, &results, &ctx, None)
174 .map_err(|e| e.to_string())?;
175 Ok(String::from_utf8(output).unwrap())
176 }
177 _ => Err("Unsupported format. Use: xml or json".to_string()),
178 }
179 }
180
181 pub fn stream_query_results(
186 &self,
187 query: &str,
188 chunk_size: usize,
189 _session_id: u64,
190 ) -> Result<Vec<String>, String> {
191 let (sparql_query, ctx) = sparql_parser::parse_sparql(query)?;
193 let plan = QueryPlanner::plan(&sparql_query, &ctx)?;
194 let executor = QueryExecutor::new(self.quins);
195 let results = executor.execute(&plan, &ctx)?;
196
197 let vars = match &sparql_query {
199 SparqlQuery::Select(select) => select.variables[..select.var_count as usize].to_vec(),
200 _ => vec![],
201 };
202
203 let mut chunks = Vec::new();
205 for chunk in results.chunks(chunk_size) {
206 let mut output = Vec::new();
207 ResultFormatter::format_json(&mut output, &vars, chunk, &ctx, None)
208 .map_err(|e| e.to_string())?;
209 chunks.push(String::from_utf8(output).unwrap());
210 }
211
212 Ok(chunks)
213 }
214
215 pub fn subscribe_session(&mut self, session_id: u64, query: &str) -> Result<u64, String> {
220 for i in 0..self.session_count as usize {
222 if let Some(session) = self.sessions[i] {
223 if session.session_id == session_id {
224 let query_hash = crate::lexicon::generate_60bit_token(query.as_bytes());
226 self.sessions[i] = Some(WebSocketSession {
227 session_id,
228 active_query: Some(query_hash),
229 subscribed: true,
230 });
231 return Ok(query_hash);
232 }
233 }
234 }
235 Err("Session not found".to_string())
236 }
237
238 pub fn unsubscribe_session(&mut self, session_id: u64) -> Result<(), String> {
240 for i in 0..self.session_count as usize {
241 if let Some(session) = self.sessions[i] {
242 if session.session_id == session_id {
243 self.sessions[i] = Some(WebSocketSession {
244 session_id,
245 active_query: None,
246 subscribed: false,
247 });
248 return Ok(());
249 }
250 }
251 }
252 Err("Session not found".to_string())
253 }
254
255 pub fn notify_session_subscribers(&self, query_hash: u64) -> Vec<u64> {
258 let mut subscribers = Vec::new();
259
260 for i in 0..self.session_count as usize {
261 if let Some(session) = self.sessions[i] {
262 if session.subscribed && session.active_query == Some(query_hash) {
263 subscribers.push(session.session_id);
264 }
265 }
266 }
267
268 subscribers
269 }
270
271 pub fn subscribe(&mut self, client_id: &str, query: &str) -> Result<String, String> {
284 let token = crate::lexicon::generate_60bit_token(query.as_bytes());
285 let subscription_id = format!("sub-{}-{}", client_id, token);
286 let subscription = Subscription::new(
287 subscription_id.clone(),
288 client_id.to_string(),
289 query.to_string(),
290 );
291 self.subscriptions
292 .insert(subscription_id.clone(), subscription);
293 Ok(subscription_id)
294 }
295
296 pub fn unsubscribe(&mut self, subscription_id: &str) -> Result<(), String> {
299 if self.subscriptions.remove(subscription_id).is_some() {
300 Ok(())
301 } else {
302 Err("Subscription not found".to_string())
303 }
304 }
305
306 pub fn get_subscriptions(&self) -> Vec<&Subscription> {
308 self.subscriptions.values().collect()
309 }
310
311 pub fn subscription_count(&self) -> usize {
313 self.subscriptions.len()
314 }
315
316 pub fn notify_subscribers(&self, event_data: &str) -> Vec<(String, String)> {
323 let mut notifications = Vec::new();
324 for sub in self.subscriptions.values() {
325 let message = format_notification(&sub.subscription_id, &sub.query, event_data);
326 notifications.push((sub.client_id.clone(), message));
327 }
328 notifications
329 }
330
331 pub fn handle_query(&self, query: &str) -> Result<Vec<u8>, String> {
334 let (sparql_query, ctx) = sparql_parser::parse_sparql(query)?;
336
337 let plan = QueryPlanner::plan(&sparql_query, &ctx)?;
339
340 let executor = QueryExecutor::new(self.quins);
342 let results = executor.execute(&plan, &ctx)?;
343
344 let vars = match &sparql_query {
346 SparqlQuery::Select(select) => select.variables[..select.var_count as usize].to_vec(),
347 _ => vec![],
348 };
349 let mut output = Vec::new();
350 ResultFormatter::format_json(&mut output, &vars, &results, &ctx, None)
351 .map_err(|e| e.to_string())?;
352 Ok(output)
353 }
354
355 pub fn stream_results(&self, results: &[u8], chunk_size: usize) -> Vec<Vec<u8>> {
359 if chunk_size == 0 {
360 return vec![results.to_vec()];
363 }
364 results.chunks(chunk_size).map(|c| c.to_vec()).collect()
365 }
366}
367
368impl<'a> Default for SparqlWebSocketHandler<'a> {
369 fn default() -> Self {
370 Self::new(&[])
371 }
372}
373
374fn format_notification(subscription_id: &str, query: &str, event_data: &str) -> String {
380 format!(
381 "{{\"subscriptionId\":\"{}\",\"query\":\"{}\",\"event\":{}}}",
382 escape_json_string(subscription_id),
383 escape_json_string(query),
384 event_data
385 )
386}
387
388fn escape_json_string(s: &str) -> String {
391 let mut out = String::with_capacity(s.len());
392 for c in s.chars() {
393 match c {
394 '"' => out.push_str("\\\""),
395 '\\' => out.push_str("\\\\"),
396 '\n' => out.push_str("\\n"),
397 '\r' => out.push_str("\\r"),
398 '\t' => out.push_str("\\t"),
399 c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
400 c => out.push(c),
401 }
402 }
403 out
404}
405
406#[cfg(test)]
407mod tests {
408 use super::*;
409
410 #[test]
411 fn test_websocket_handler_creation() {
412 let quins = vec![];
413 let handler = SparqlWebSocketHandler::new(&quins);
414 assert_eq!(handler.session_count, 0);
415 }
416
417 #[test]
418 fn test_register_session() {
419 let quins = vec![];
420 let mut handler = SparqlWebSocketHandler::new(&quins);
421
422 let session_id = handler.register_session().unwrap();
423 assert_eq!(session_id, 0);
424 assert_eq!(handler.session_count, 1);
425 }
426
427 #[test]
428 fn test_unregister_session() {
429 let quins = vec![];
430 let mut handler = SparqlWebSocketHandler::new(&quins);
431
432 let session_id = handler.register_session().unwrap();
433 handler.unregister_session(session_id).unwrap();
434 }
435
436 #[test]
437 fn test_subscribe_session() {
438 let quins = vec![];
439 let mut handler = SparqlWebSocketHandler::new(&quins);
440
441 let session_id = handler.register_session().unwrap();
442 let query_hash = handler
443 .subscribe_session(session_id, "SELECT ?s WHERE ?s ?p ?o")
444 .unwrap();
445
446 assert!(query_hash > 0);
447 }
448
449 #[test]
452 fn test_handler_initialize() {
453 let quins = vec![];
454 let mut handler = SparqlWebSocketHandler::new(&quins);
455 assert!(!handler.initialized);
456 handler.initialize().unwrap();
457 assert!(handler.initialized);
458 assert_eq!(handler.subscription_count(), 0);
459 }
460
461 #[test]
462 fn test_subscribe_client_returns_id() {
463 let quins = vec![];
464 let mut handler = SparqlWebSocketHandler::new(&quins);
465
466 let sub_id = handler
467 .subscribe("client-1", "SELECT ?s WHERE { ?s ?p ?o }")
468 .unwrap();
469 assert!(sub_id.starts_with("sub-client-1-"));
470 assert_eq!(handler.subscription_count(), 1);
471 }
472
473 #[test]
474 fn test_subscribe_client_stores_query_and_client() {
475 let quins = vec![];
476 let mut handler = SparqlWebSocketHandler::new(&quins);
477
478 let sub_id = handler
479 .subscribe("client-1", "SELECT ?s WHERE { ?s ?p ?o }")
480 .unwrap();
481 let subs = handler.get_subscriptions();
482 assert_eq!(subs.len(), 1);
483 assert_eq!(subs[0].subscription_id, sub_id);
484 assert_eq!(subs[0].client_id, "client-1");
485 assert_eq!(subs[0].query, "SELECT ?s WHERE { ?s ?p ?o }");
486 }
487
488 #[test]
489 fn test_subscribe_same_query_same_client_stable_id() {
490 let quins = vec![];
491 let mut handler = SparqlWebSocketHandler::new(&quins);
492
493 let id1 = handler
494 .subscribe("client-1", "SELECT ?s WHERE { ?s ?p ?o }")
495 .unwrap();
496 let id2 = handler
497 .subscribe("client-1", "SELECT ?s WHERE { ?s ?p ?o }")
498 .unwrap();
499 assert_eq!(id1, id2);
501 assert_eq!(handler.subscription_count(), 1);
502 }
503
504 #[test]
505 fn test_subscribe_different_clients_separate_subscriptions() {
506 let quins = vec![];
507 let mut handler = SparqlWebSocketHandler::new(&quins);
508
509 handler
510 .subscribe("client-1", "SELECT ?s WHERE { ?s ?p ?o }")
511 .unwrap();
512 handler
513 .subscribe("client-2", "SELECT ?s WHERE { ?s ?p ?o }")
514 .unwrap();
515 assert_eq!(handler.subscription_count(), 2);
516 }
517
518 #[test]
519 fn test_unsubscribe_client_removes_subscription() {
520 let quins = vec![];
521 let mut handler = SparqlWebSocketHandler::new(&quins);
522
523 let sub_id = handler
524 .subscribe("client-1", "SELECT ?s WHERE { ?s ?p ?o }")
525 .unwrap();
526 assert_eq!(handler.subscription_count(), 1);
527
528 handler.unsubscribe(&sub_id).unwrap();
529 assert_eq!(handler.subscription_count(), 0);
530 assert!(handler.get_subscriptions().is_empty());
531 }
532
533 #[test]
534 fn test_unsubscribe_client_unknown_id_errors() {
535 let quins = vec![];
536 let mut handler = SparqlWebSocketHandler::new(&quins);
537 let err = handler.unsubscribe("does-not-exist").unwrap_err();
538 assert!(err.contains("not found"));
539 }
540
541 #[test]
542 fn test_subscription_lifecycle() {
543 let quins = vec![];
544 let mut handler = SparqlWebSocketHandler::new(&quins);
545 handler.initialize().unwrap();
546
547 let id1 = handler
549 .subscribe("client-1", "SELECT ?s WHERE { ?s ?p ?o }")
550 .unwrap();
551 let id2 = handler
552 .subscribe("client-2", "SELECT ?s WHERE { ?s ?p ?o }")
553 .unwrap();
554 assert_eq!(handler.subscription_count(), 2);
555
556 let notifications = handler.notify_subscribers("{\"type\":\"update\"}");
558 assert_eq!(notifications.len(), 2);
559 let client_ids: Vec<&str> = notifications.iter().map(|(c, _)| c.as_str()).collect();
560 assert!(client_ids.contains(&"client-1"));
561 assert!(client_ids.contains(&"client-2"));
562 for (_, msg) in ¬ifications {
563 assert!(msg.contains("\"query\":\"SELECT ?s WHERE { ?s ?p ?o }\""));
564 assert!(msg.contains("\"event\":{\"type\":\"update\"}"));
565 }
566
567 handler.unsubscribe(&id1).unwrap();
569 assert_eq!(handler.subscription_count(), 1);
570 let notifications = handler.notify_subscribers("{\"type\":\"update\"}");
571 assert_eq!(notifications.len(), 1);
572 assert_eq!(notifications[0].0, "client-2");
573
574 handler.unsubscribe(&id2).unwrap();
576 assert_eq!(handler.subscription_count(), 0);
577 assert!(handler.notify_subscribers("event").is_empty());
578 }
579
580 #[test]
581 fn test_notify_subscribers_message_format() {
582 let quins = vec![];
583 let mut handler = SparqlWebSocketHandler::new(&quins);
584 handler
585 .subscribe("client-1", "SELECT ?s WHERE { ?s ?p ?o }")
586 .unwrap();
587
588 let notifications = handler.notify_subscribers("{\"data\":42}");
589 assert_eq!(notifications.len(), 1);
590 let (_client, msg) = ¬ifications[0];
591 assert!(msg.starts_with("{\"subscriptionId\":\""));
592 assert!(msg.contains("\"query\":\"SELECT ?s WHERE { ?s ?p ?o }\""));
593 assert!(msg.contains("\"event\":{\"data\":42}"));
594 }
595
596 #[test]
597 fn test_handle_query_bytes_returns_json() {
598 let quins = vec![];
599 let handler = SparqlWebSocketHandler::new(&quins);
600 let bytes = handler
601 .handle_query("SELECT ?s WHERE { ?s ?p ?o }")
602 .unwrap();
603 let text = String::from_utf8(bytes).unwrap();
605 assert!(text.contains("head") || text.contains("results") || text.contains('{'));
606 }
607
608 #[test]
609 fn test_handle_query_bytes_parse_error() {
610 let quins = vec![];
611 let handler = SparqlWebSocketHandler::new(&quins);
612 let err = handler.handle_query("not sparql").unwrap_err();
613 assert!(!err.is_empty());
614 }
615
616 #[test]
617 fn test_stream_results_bytes_chunks() {
618 let quins = vec![];
619 let handler = SparqlWebSocketHandler::new(&quins);
620 let data: Vec<u8> = (0..25u8).collect();
621 let chunks = handler.stream_results(&data, 10);
622 assert_eq!(chunks.len(), 3);
623 assert_eq!(chunks[0].len(), 10);
624 assert_eq!(chunks[1].len(), 10);
625 assert_eq!(chunks[2].len(), 5);
626 let reassembled: Vec<u8> = chunks.into_iter().flatten().collect();
628 assert_eq!(reassembled, data);
629 }
630
631 #[test]
632 fn test_stream_results_bytes_empty() {
633 let quins = vec![];
634 let handler = SparqlWebSocketHandler::new(&quins);
635 let chunks = handler.stream_results(&[], 10);
636 assert!(chunks.is_empty());
637 }
638
639 #[test]
640 fn test_stream_results_bytes_zero_chunk_size() {
641 let quins = vec![];
642 let handler = SparqlWebSocketHandler::new(&quins);
643 let data: Vec<u8> = (0..5u8).collect();
644 let chunks = handler.stream_results(&data, 0);
646 assert_eq!(chunks.len(), 1);
647 assert_eq!(chunks[0], data);
648 }
649
650 #[test]
651 fn test_format_notification_escapes_quotes() {
652 let msg = format_notification("sub-1", "SELECT \"x\"", "{}");
653 assert!(msg.contains("\\\"x\\\""));
654 assert!(msg.contains("\"event\":{}"));
655 }
656
657 #[test]
658 fn test_escape_json_string() {
659 assert_eq!(escape_json_string("a\"b"), "a\\\"b");
660 assert_eq!(escape_json_string("a\\b"), "a\\\\b");
661 assert_eq!(escape_json_string("a\nb"), "a\\nb");
662 assert_eq!(escape_json_string("plain"), "plain");
663 }
664}