Skip to main content

oxirs_gql/
subscriptions.rs

1//! WebSocket-based GraphQL subscription system
2//!
3//! This module provides real-time GraphQL subscriptions over WebSocket connections,
4//! enabling clients to receive live updates when RDF data changes.
5
6use crate::ast::{Document, Value};
7use crate::execution::FieldResolver;
8use crate::execution::{ExecutionContext, QueryExecutor};
9use crate::types::Schema;
10use anyhow::{anyhow, Result};
11use futures_util::{SinkExt, StreamExt};
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14use std::sync::Arc;
15use std::time::{Duration, Instant};
16use tokio::net::{TcpListener, TcpStream};
17use tokio::sync::{broadcast, RwLock};
18use tokio::time::interval;
19use tokio_tungstenite::{accept_async, tungstenite::Message, WebSocketStream};
20use tracing::{debug, error, info, warn};
21use uuid::Uuid;
22
23/// GraphQL subscription message types (following graphql-ws protocol)
24#[derive(Debug, Clone, Serialize, Deserialize)]
25#[serde(tag = "type")]
26pub enum SubscriptionMessage {
27    #[serde(rename = "connection_init")]
28    ConnectionInit { payload: Option<serde_json::Value> },
29    #[serde(rename = "connection_ack")]
30    ConnectionAck,
31    #[serde(rename = "connection_error")]
32    ConnectionError { payload: Option<serde_json::Value> },
33    #[serde(rename = "connection_terminate")]
34    ConnectionTerminate,
35    #[serde(rename = "start")]
36    Start {
37        id: String,
38        payload: SubscriptionPayload,
39    },
40    #[serde(rename = "data")]
41    Data {
42        id: String,
43        payload: serde_json::Value,
44    },
45    #[serde(rename = "error")]
46    Error {
47        id: String,
48        payload: serde_json::Value,
49    },
50    #[serde(rename = "complete")]
51    Complete { id: String },
52    #[serde(rename = "stop")]
53    Stop { id: String },
54    #[serde(rename = "ka")]
55    KeepAlive,
56}
57
58/// Subscription payload containing the GraphQL query and variables
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct SubscriptionPayload {
61    pub query: String,
62    pub variables: Option<HashMap<String, Value>>,
63    pub operation_name: Option<String>,
64}
65
66/// Active subscription tracking information
67#[derive(Debug, Clone)]
68pub struct ActiveSubscription {
69    pub id: String,
70    pub connection_id: String,
71    pub document: Document,
72    pub context: ExecutionContext,
73    pub created_at: Instant,
74    pub last_execution: Option<Instant>,
75    pub execution_count: u64,
76}
77
78/// Subscription change event
79#[derive(Debug, Clone)]
80pub enum SubscriptionEvent {
81    TripleAdded {
82        subject: String,
83        predicate: String,
84        object: String,
85    },
86    TripleRemoved {
87        subject: String,
88        predicate: String,
89        object: String,
90    },
91    SubjectChanged {
92        subject: String,
93    },
94    PredicateChanged {
95        predicate: String,
96    },
97    BulkChange,
98}
99
100/// WebSocket connection state
101#[derive(Debug)]
102pub struct WebSocketConnection {
103    pub id: String,
104    pub socket: Arc<RwLock<WebSocketStream<TcpStream>>>,
105    pub subscriptions: Arc<RwLock<HashMap<String, ActiveSubscription>>>,
106    pub last_ping: Arc<RwLock<Instant>>,
107    pub authenticated: Arc<RwLock<bool>>,
108}
109
110/// Authentication method for WebSocket connections
111#[derive(Debug, Clone)]
112pub enum AuthenticationMethod {
113    /// No authentication required
114    None,
115    /// Bearer token authentication
116    BearerToken { valid_tokens: Vec<String> },
117    /// API key authentication
118    ApiKey { valid_keys: Vec<String> },
119    /// JWT token authentication (simplified validation)
120    JWT { secret: String },
121}
122
123/// Subscription manager configuration
124#[derive(Debug, Clone)]
125pub struct SubscriptionConfig {
126    pub max_subscriptions_per_connection: usize,
127    pub max_total_subscriptions: usize,
128    pub keepalive_interval: Duration,
129    pub subscription_timeout: Duration,
130    pub enable_authentication: bool,
131    pub max_execution_frequency: Duration,
132    pub auth_method: AuthenticationMethod,
133}
134
135impl Default for SubscriptionConfig {
136    fn default() -> Self {
137        Self {
138            max_subscriptions_per_connection: 10,
139            max_total_subscriptions: 1000,
140            keepalive_interval: Duration::from_secs(30),
141            subscription_timeout: Duration::from_secs(300),
142            enable_authentication: false,
143            max_execution_frequency: Duration::from_millis(100),
144            auth_method: AuthenticationMethod::None,
145        }
146    }
147}
148
149/// WebSocket-based subscription manager
150pub struct SubscriptionManager {
151    config: SubscriptionConfig,
152    connections: Arc<RwLock<HashMap<String, Arc<WebSocketConnection>>>>,
153    active_subscriptions: Arc<RwLock<HashMap<String, ActiveSubscription>>>,
154    event_sender: broadcast::Sender<SubscriptionEvent>,
155    schema: Arc<RwLock<Schema>>,
156    executor: Arc<QueryExecutor>,
157    resolvers: Arc<RwLock<HashMap<String, Arc<dyn FieldResolver>>>>,
158}
159
160impl SubscriptionManager {
161    pub fn new(config: SubscriptionConfig, schema: Schema, executor: QueryExecutor) -> Self {
162        let (event_sender, _) = broadcast::channel(1000);
163
164        Self {
165            config,
166            connections: Arc::new(RwLock::new(HashMap::new())),
167            active_subscriptions: Arc::new(RwLock::new(HashMap::new())),
168            event_sender,
169            schema: Arc::new(RwLock::new(schema)),
170            executor: Arc::new(executor),
171            resolvers: Arc::new(RwLock::new(HashMap::new())),
172        }
173    }
174
175    /// Start the subscription server on the given address
176    pub async fn start_server(&self, addr: &str) -> Result<()> {
177        let listener = TcpListener::bind(addr).await?;
178        info!("GraphQL subscription server listening on {}", addr);
179
180        // Start background tasks
181        self.start_keepalive_task().await;
182        self.start_cleanup_task().await;
183
184        // Accept incoming connections
185        while let Ok((stream, addr)) = listener.accept().await {
186            info!("New WebSocket connection from {}", addr);
187
188            let manager = self.clone();
189            tokio::spawn(async move {
190                if let Err(e) = manager.handle_connection(stream).await {
191                    error!("Error handling WebSocket connection: {}", e);
192                }
193            });
194        }
195
196        Ok(())
197    }
198
199    /// Handle a new WebSocket connection
200    async fn handle_connection(&self, stream: TcpStream) -> Result<()> {
201        let ws_stream = accept_async(stream).await?;
202        let connection_id = Uuid::new_v4().to_string();
203
204        let connection = Arc::new(WebSocketConnection {
205            id: connection_id.clone(),
206            socket: Arc::new(RwLock::new(ws_stream)),
207            subscriptions: Arc::new(RwLock::new(HashMap::new())),
208            last_ping: Arc::new(RwLock::new(Instant::now())),
209            authenticated: Arc::new(RwLock::new(!self.config.enable_authentication)),
210        });
211
212        // Register connection
213        {
214            let mut connections = self.connections.write().await;
215            connections.insert(connection_id.clone(), connection.clone());
216        }
217
218        // Handle messages
219        let result = self.handle_connection_messages(connection.clone()).await;
220
221        // Cleanup on disconnect
222        self.cleanup_connection(&connection_id).await;
223
224        result
225    }
226
227    /// Handle WebSocket messages for a connection
228    async fn handle_connection_messages(&self, connection: Arc<WebSocketConnection>) -> Result<()> {
229        let mut event_receiver = self.event_sender.subscribe();
230
231        loop {
232            tokio::select! {
233                // Handle incoming WebSocket messages
234                message_result = async {
235                    let mut socket = connection.socket.write().await;
236                    socket.next().await
237                } => {
238                    match message_result {
239                        Some(Ok(msg)) => {
240                            if let Err(e) = self.handle_websocket_message(&connection, msg).await {
241                                error!("Error handling WebSocket message: {}", e);
242                                break;
243                            }
244                        }
245                        Some(Err(e)) => {
246                            error!("WebSocket error: {}", e);
247                            break;
248                        }
249                        None => {
250                            debug!("WebSocket connection closed");
251                            break;
252                        }
253                    }
254                }
255
256                // Handle subscription events
257                event_result = event_receiver.recv() => {
258                    match event_result {
259                        Ok(event) => {
260                            if let Err(e) = self.handle_subscription_event(&connection, &event).await {
261                                error!("Error handling subscription event: {}", e);
262                            }
263                        }
264                        Err(broadcast::error::RecvError::Lagged(_)) => {
265                            warn!("Subscription event receiver lagged, continuing...");
266                        }
267                        Err(broadcast::error::RecvError::Closed) => {
268                            info!("Event channel closed");
269                            break;
270                        }
271                    }
272                }
273            }
274        }
275
276        Ok(())
277    }
278
279    /// Handle a single WebSocket message
280    async fn handle_websocket_message(
281        &self,
282        connection: &Arc<WebSocketConnection>,
283        message: Message,
284    ) -> Result<()> {
285        match message {
286            Message::Text(text) => {
287                let msg: SubscriptionMessage = serde_json::from_str(&text)?;
288                self.handle_subscription_message(connection, msg).await
289            }
290            Message::Close(_) => {
291                info!("WebSocket connection closed by client");
292                Ok(())
293            }
294            Message::Ping(data) => {
295                let mut socket = connection.socket.write().await;
296                socket.send(Message::Pong(data)).await?;
297                Ok(())
298            }
299            Message::Pong(_) => {
300                // Update last ping time
301                *connection.last_ping.write().await = Instant::now();
302                Ok(())
303            }
304            _ => Ok(()), // Ignore other message types
305        }
306    }
307
308    /// Handle a GraphQL subscription message
309    async fn handle_subscription_message(
310        &self,
311        connection: &Arc<WebSocketConnection>,
312        message: SubscriptionMessage,
313    ) -> Result<()> {
314        match message {
315            SubscriptionMessage::ConnectionInit { payload } => {
316                // Handle authentication if enabled
317                let is_authenticated = if self.config.enable_authentication {
318                    let auth_result = self.authenticate_connection(payload).await?;
319
320                    if !auth_result {
321                        let error = SubscriptionMessage::ConnectionError {
322                            payload: Some(serde_json::json!({
323                                "message": "Authentication failed",
324                                "code": "AUTH_FAILED"
325                            })),
326                        };
327                        self.send_message(connection, &error).await?;
328                        return Ok(());
329                    }
330                    auth_result
331                } else {
332                    // If authentication is disabled, automatically authenticate
333                    true
334                };
335
336                // Set authentication status
337                *connection.authenticated.write().await = is_authenticated;
338
339                // Send acknowledgment
340                let ack = SubscriptionMessage::ConnectionAck;
341                self.send_message(connection, &ack).await?;
342            }
343
344            SubscriptionMessage::Start { id, payload } => {
345                if !*connection.authenticated.read().await {
346                    let error = SubscriptionMessage::ConnectionError {
347                        payload: Some(serde_json::json!({"message": "Not authenticated"})),
348                    };
349                    self.send_message(connection, &error).await?;
350                    return Ok(());
351                }
352
353                self.start_subscription(connection, &id, payload).await?;
354            }
355
356            SubscriptionMessage::Stop { id } => {
357                self.stop_subscription(connection, &id).await?;
358            }
359
360            SubscriptionMessage::ConnectionTerminate => {
361                info!("Connection terminated by client");
362                return Err(anyhow!("Connection terminated"));
363            }
364
365            _ => {
366                warn!("Unexpected message type received");
367            }
368        }
369
370        Ok(())
371    }
372
373    /// Start a new subscription
374    async fn start_subscription(
375        &self,
376        connection: &Arc<WebSocketConnection>,
377        subscription_id: &str,
378        payload: SubscriptionPayload,
379    ) -> Result<()> {
380        // Check subscription limits
381        let connection_sub_count = {
382            let connection_subscriptions = connection.subscriptions.read().await;
383            connection_subscriptions.len()
384        };
385
386        if connection_sub_count >= self.config.max_subscriptions_per_connection {
387            let error = SubscriptionMessage::Error {
388                id: subscription_id.to_string(),
389                payload: serde_json::json!({"message": "Too many subscriptions for this connection"}),
390            };
391            self.send_message(connection, &error).await?;
392            return Ok(());
393        }
394
395        let total_sub_count = {
396            let active_subscriptions = self.active_subscriptions.read().await;
397            active_subscriptions.len()
398        };
399
400        if total_sub_count >= self.config.max_total_subscriptions {
401            let error = SubscriptionMessage::Error {
402                id: subscription_id.to_string(),
403                payload: serde_json::json!({"message": "Server subscription limit reached"}),
404            };
405            self.send_message(connection, &error).await?;
406            return Ok(());
407        }
408
409        // Parse the GraphQL document
410        let document = match crate::parser::parse_document(&payload.query) {
411            Ok(doc) => doc,
412            Err(e) => {
413                let error = SubscriptionMessage::Error {
414                    id: subscription_id.to_string(),
415                    payload: serde_json::json!({"message": format!("Parse error: {}", e)}),
416                };
417                self.send_message(connection, &error).await?;
418                return Ok(());
419            }
420        };
421
422        // Create execution context
423        let mut context = ExecutionContext::new();
424        if let Some(variables) = payload.variables {
425            context = context.with_variables(variables);
426        }
427        if let Some(operation_name) = payload.operation_name {
428            context = context.with_operation_name(operation_name);
429        }
430
431        // Create subscription
432        let subscription = ActiveSubscription {
433            id: subscription_id.to_string(),
434            connection_id: connection.id.clone(),
435            document,
436            context,
437            created_at: Instant::now(),
438            last_execution: None,
439            execution_count: 0,
440        };
441
442        // Register subscription
443        {
444            let mut connection_subscriptions = connection.subscriptions.write().await;
445            connection_subscriptions.insert(subscription_id.to_string(), subscription.clone());
446        }
447
448        {
449            let mut active_subscriptions = self.active_subscriptions.write().await;
450            active_subscriptions.insert(subscription_id.to_string(), subscription.clone());
451        }
452
453        // Execute initial subscription
454        self.execute_subscription(&subscription).await?;
455
456        info!(
457            "Started subscription {} for connection {}",
458            subscription_id, connection.id
459        );
460        Ok(())
461    }
462
463    /// Stop a subscription
464    async fn stop_subscription(
465        &self,
466        connection: &Arc<WebSocketConnection>,
467        subscription_id: &str,
468    ) -> Result<()> {
469        // Remove from connection
470        {
471            let mut connection_subscriptions = connection.subscriptions.write().await;
472            connection_subscriptions.remove(subscription_id);
473        }
474
475        // Remove from active subscriptions
476        {
477            let mut active_subscriptions = self.active_subscriptions.write().await;
478            active_subscriptions.remove(subscription_id);
479        }
480
481        // Send completion message
482        let complete = SubscriptionMessage::Complete {
483            id: subscription_id.to_string(),
484        };
485        self.send_message(connection, &complete).await?;
486
487        info!(
488            "Stopped subscription {} for connection {}",
489            subscription_id, connection.id
490        );
491        Ok(())
492    }
493
494    /// Execute a subscription and send results
495    async fn execute_subscription(&self, subscription: &ActiveSubscription) -> Result<()> {
496        // Check execution frequency limit
497        if let Some(last_execution) = subscription.last_execution {
498            let elapsed = Instant::now().duration_since(last_execution);
499            if elapsed < self.config.max_execution_frequency {
500                return Ok(()); // Skip execution to avoid spam
501            }
502        }
503
504        let result = self
505            .executor
506            .execute(&subscription.document, &subscription.context)
507            .await?;
508
509        // Find the connection
510        let connection = {
511            let connections = self.connections.read().await;
512            connections.get(&subscription.connection_id).cloned()
513        };
514
515        if let Some(connection) = connection {
516            if result.has_errors() {
517                let error = SubscriptionMessage::Error {
518                    id: subscription.id.clone(),
519                    payload: serde_json::to_value(&result.errors)?,
520                };
521                self.send_message(&connection, &error).await?;
522            } else if let Some(data) = result.data {
523                let data_msg = SubscriptionMessage::Data {
524                    id: subscription.id.clone(),
525                    payload: data,
526                };
527                self.send_message(&connection, &data_msg).await?;
528            }
529
530            // Update subscription execution info
531            {
532                let mut active_subscriptions = self.active_subscriptions.write().await;
533                if let Some(sub) = active_subscriptions.get_mut(&subscription.id) {
534                    sub.last_execution = Some(Instant::now());
535                    sub.execution_count += 1;
536                }
537            }
538        }
539
540        Ok(())
541    }
542
543    /// Handle subscription events and trigger re-execution
544    async fn handle_subscription_event(
545        &self,
546        connection: &Arc<WebSocketConnection>,
547        event: &SubscriptionEvent,
548    ) -> Result<()> {
549        let subscriptions: Vec<ActiveSubscription> = {
550            let connection_subscriptions = connection.subscriptions.read().await;
551            connection_subscriptions.values().cloned().collect()
552        };
553
554        for subscription in subscriptions {
555            // Determine if this subscription should be re-executed based on the event
556            if self.should_execute_for_event(&subscription, event) {
557                if let Err(e) = self.execute_subscription(&subscription).await {
558                    error!("Error executing subscription {}: {}", subscription.id, e);
559                }
560            }
561        }
562
563        Ok(())
564    }
565
566    /// Determine if a subscription should be re-executed for a given event
567    fn should_execute_for_event(
568        &self,
569        subscription: &ActiveSubscription,
570        event: &SubscriptionEvent,
571    ) -> bool {
572        match event {
573            SubscriptionEvent::BulkChange => true, // Always re-execute for bulk changes
574            SubscriptionEvent::TripleAdded {
575                subject,
576                predicate,
577                object: _,
578            }
579            | SubscriptionEvent::TripleRemoved {
580                subject,
581                predicate,
582                object: _,
583            } => {
584                // Check if the subscription query involves this subject or predicate
585                self.subscription_involves_resource(subscription, subject)
586                    || self.subscription_involves_resource(subscription, predicate)
587            }
588            SubscriptionEvent::SubjectChanged { subject } => {
589                self.subscription_involves_resource(subscription, subject)
590            }
591            SubscriptionEvent::PredicateChanged { predicate } => {
592                self.subscription_involves_resource(subscription, predicate)
593            }
594        }
595    }
596
597    /// Check if a subscription query involves a specific resource
598    fn subscription_involves_resource(
599        &self,
600        _subscription: &ActiveSubscription,
601        _resource: &str,
602    ) -> bool {
603        // This is a simplified implementation - in practice, you'd want to analyze
604        // the GraphQL query to determine which resources it depends on
605
606        // For now, we'll re-execute all subscriptions for any change
607        // A more sophisticated implementation would parse the query and track dependencies
608        true
609    }
610
611    /// Send a message to a WebSocket connection
612    async fn send_message(
613        &self,
614        connection: &Arc<WebSocketConnection>,
615        message: &SubscriptionMessage,
616    ) -> Result<()> {
617        let text = serde_json::to_string(message)?;
618        let mut socket = connection.socket.write().await;
619        socket.send(Message::Text(text.into())).await?;
620        Ok(())
621    }
622
623    /// Trigger a subscription event
624    pub fn trigger_event(&self, event: SubscriptionEvent) {
625        if let Err(e) = self.event_sender.send(event) {
626            debug!("No active subscription listeners: {}", e);
627        }
628    }
629
630    /// Start keepalive task
631    async fn start_keepalive_task(&self) {
632        let connections = Arc::clone(&self.connections);
633        let keepalive_interval = self.config.keepalive_interval;
634
635        tokio::spawn(async move {
636            let mut interval = interval(keepalive_interval);
637
638            loop {
639                interval.tick().await;
640
641                let connections_to_ping: Vec<Arc<WebSocketConnection>> = {
642                    let connections = connections.read().await;
643                    connections.values().cloned().collect()
644                };
645
646                for connection in connections_to_ping {
647                    let keepalive = SubscriptionMessage::KeepAlive;
648                    if let Ok(text) = serde_json::to_string(&keepalive) {
649                        let mut socket = connection.socket.write().await;
650                        if socket.send(Message::Text(text.into())).await.is_err() {
651                            debug!("Failed to send keepalive to connection {}", connection.id);
652                        }
653                    }
654                }
655            }
656        });
657    }
658
659    /// Start cleanup task for expired subscriptions
660    async fn start_cleanup_task(&self) {
661        let active_subscriptions = Arc::clone(&self.active_subscriptions);
662        let connections = Arc::clone(&self.connections);
663        let timeout = self.config.subscription_timeout;
664
665        tokio::spawn(async move {
666            let mut interval = interval(Duration::from_secs(60)); // Check every minute
667
668            loop {
669                interval.tick().await;
670
671                let now = Instant::now();
672                let mut expired_subscriptions = Vec::new();
673
674                {
675                    let subscriptions = active_subscriptions.read().await;
676                    for (id, subscription) in subscriptions.iter() {
677                        if now.duration_since(subscription.created_at) > timeout {
678                            expired_subscriptions.push(id.clone());
679                        }
680                    }
681                }
682
683                for subscription_id in expired_subscriptions {
684                    info!("Cleaning up expired subscription: {}", subscription_id);
685
686                    // Remove from active subscriptions
687                    let connection_id = {
688                        let mut subscriptions = active_subscriptions.write().await;
689                        subscriptions
690                            .remove(&subscription_id)
691                            .map(|sub| sub.connection_id)
692                    };
693
694                    // Remove from connection and send completion message
695                    if let Some(connection_id) = connection_id {
696                        let connection_opt = {
697                            let connections = connections.read().await;
698                            connections.get(&connection_id).cloned()
699                        };
700
701                        if let Some(connection) = connection_opt {
702                            {
703                                let mut connection_subscriptions =
704                                    connection.subscriptions.write().await;
705                                connection_subscriptions.remove(&subscription_id);
706                            }
707
708                            let complete = SubscriptionMessage::Complete {
709                                id: subscription_id.clone(),
710                            };
711
712                            if let Ok(text) = serde_json::to_string(&complete) {
713                                let mut socket = connection.socket.write().await;
714                                let _ = socket.send(Message::Text(text.into())).await;
715                            }
716                        }
717                    }
718                }
719            }
720        });
721    }
722
723    /// Cleanup connection and its subscriptions
724    async fn cleanup_connection(&self, connection_id: &str) {
725        info!("Cleaning up connection: {}", connection_id);
726
727        // Remove connection
728        {
729            let mut connections = self.connections.write().await;
730            connections.remove(connection_id);
731        }
732
733        // Remove all subscriptions for this connection
734        let subscription_ids: Vec<String> = {
735            let active_subscriptions = self.active_subscriptions.read().await;
736            active_subscriptions
737                .iter()
738                .filter(|(_, sub)| sub.connection_id == connection_id)
739                .map(|(id, _)| id.clone())
740                .collect()
741        };
742
743        {
744            let mut active_subscriptions = self.active_subscriptions.write().await;
745            for subscription_id in subscription_ids {
746                active_subscriptions.remove(&subscription_id);
747                info!(
748                    "Removed subscription {} for connection {}",
749                    subscription_id, connection_id
750                );
751            }
752        }
753    }
754
755    /// Get subscription statistics
756    pub async fn get_stats(&self) -> SubscriptionStats {
757        let connections = self.connections.read().await;
758        let active_subscriptions = self.active_subscriptions.read().await;
759
760        SubscriptionStats {
761            total_connections: connections.len(),
762            total_subscriptions: active_subscriptions.len(),
763            avg_subscriptions_per_connection: if connections.is_empty() {
764                0.0
765            } else {
766                active_subscriptions.len() as f64 / connections.len() as f64
767            },
768        }
769    }
770
771    /// Authenticate a WebSocket connection based on the provided payload
772    async fn authenticate_connection(&self, payload: Option<serde_json::Value>) -> Result<bool> {
773        if !self.config.enable_authentication {
774            return Ok(true);
775        }
776
777        let payload = match payload {
778            Some(p) => p,
779            None => {
780                debug!("No authentication payload provided");
781                return Ok(false);
782            }
783        };
784
785        match &self.config.auth_method {
786            AuthenticationMethod::None => Ok(true),
787
788            AuthenticationMethod::BearerToken { valid_tokens } => {
789                if let Some(token) = payload
790                    .get("authorization")
791                    .or_else(|| payload.get("Authorization"))
792                    .and_then(|v| v.as_str())
793                {
794                    // Remove "Bearer " prefix if present
795                    let token = token.strip_prefix("Bearer ").unwrap_or(token);
796
797                    if valid_tokens.contains(&token.to_string()) {
798                        info!("WebSocket connection authenticated with Bearer token");
799                        Ok(true)
800                    } else {
801                        warn!("Invalid Bearer token provided");
802                        Ok(false)
803                    }
804                } else {
805                    warn!("No authorization token found in payload");
806                    Ok(false)
807                }
808            }
809
810            AuthenticationMethod::ApiKey { valid_keys } => {
811                if let Some(api_key) = payload
812                    .get("apiKey")
813                    .or_else(|| payload.get("api_key"))
814                    .and_then(|v| v.as_str())
815                {
816                    if valid_keys.contains(&api_key.to_string()) {
817                        info!("WebSocket connection authenticated with API key");
818                        Ok(true)
819                    } else {
820                        warn!("Invalid API key provided");
821                        Ok(false)
822                    }
823                } else {
824                    warn!("No API key found in payload");
825                    Ok(false)
826                }
827            }
828
829            AuthenticationMethod::JWT { secret: _ } => {
830                // Simplified JWT validation - in production, use a proper JWT library
831                if let Some(jwt) = payload
832                    .get("jwt")
833                    .or_else(|| payload.get("token"))
834                    .and_then(|v| v.as_str())
835                {
836                    // Basic JWT structure validation (header.payload.signature)
837                    let parts: Vec<&str> = jwt.split('.').collect();
838                    if parts.len() == 3 {
839                        // In a real implementation, you would:
840                        // 1. Decode and verify the signature
841                        // 2. Check expiration
842                        // 3. Validate claims
843                        info!("WebSocket connection authenticated with JWT (basic validation)");
844                        Ok(true)
845                    } else {
846                        warn!("Invalid JWT format");
847                        Ok(false)
848                    }
849                } else {
850                    warn!("No JWT token found in payload");
851                    Ok(false)
852                }
853            }
854        }
855    }
856}
857
858/// Subscription server statistics
859#[derive(Debug, Clone, Serialize)]
860pub struct SubscriptionStats {
861    pub total_connections: usize,
862    pub total_subscriptions: usize,
863    pub avg_subscriptions_per_connection: f64,
864}
865
866impl Clone for SubscriptionManager {
867    fn clone(&self) -> Self {
868        Self {
869            config: self.config.clone(),
870            connections: Arc::clone(&self.connections),
871            active_subscriptions: Arc::clone(&self.active_subscriptions),
872            event_sender: self.event_sender.clone(),
873            schema: Arc::clone(&self.schema),
874            executor: Arc::clone(&self.executor),
875            resolvers: Arc::clone(&self.resolvers),
876        }
877    }
878}
879
880#[cfg(test)]
881mod tests {
882    use super::*;
883    use crate::types::{BuiltinScalars, FieldType, ObjectType, Schema};
884
885    fn create_test_schema() -> Schema {
886        let mut schema = Schema::new();
887
888        let query_type = ObjectType::new("Query".to_string()).with_field(
889            "hello".to_string(),
890            FieldType::new(
891                "hello".to_string(),
892                crate::types::GraphQLType::Scalar(BuiltinScalars::string()),
893            ),
894        );
895
896        schema.add_type(crate::types::GraphQLType::Object(query_type));
897        schema.set_query_type("Query".to_string());
898
899        schema
900    }
901
902    #[test]
903    fn test_subscription_config() {
904        let config = SubscriptionConfig::default();
905        assert_eq!(config.max_subscriptions_per_connection, 10);
906        assert_eq!(config.max_total_subscriptions, 1000);
907        assert!(!config.enable_authentication);
908    }
909
910    #[test]
911    fn test_subscription_message_serialization() {
912        let msg = SubscriptionMessage::ConnectionInit {
913            payload: Some(serde_json::json!({"auth": "token"})),
914        };
915
916        let serialized = serde_json::to_string(&msg).expect("should succeed");
917        let deserialized: SubscriptionMessage =
918            serde_json::from_str(&serialized).expect("should succeed");
919
920        matches!(deserialized, SubscriptionMessage::ConnectionInit { .. });
921    }
922
923    #[test]
924    fn test_subscription_payload() {
925        let payload = SubscriptionPayload {
926            query: "subscription { hello }".to_string(),
927            variables: Some(HashMap::new()),
928            operation_name: None,
929        };
930
931        assert_eq!(payload.query, "subscription { hello }");
932        assert!(payload.variables.is_some());
933        assert!(payload.operation_name.is_none());
934    }
935
936    #[tokio::test]
937    async fn test_subscription_manager_creation() {
938        let config = SubscriptionConfig::default();
939        let schema = create_test_schema();
940        let executor = QueryExecutor::new(schema.clone());
941
942        let manager = SubscriptionManager::new(config, schema, executor);
943        let stats = manager.get_stats().await;
944
945        assert_eq!(stats.total_connections, 0);
946        assert_eq!(stats.total_subscriptions, 0);
947        assert_eq!(stats.avg_subscriptions_per_connection, 0.0);
948    }
949
950    #[test]
951    fn test_subscription_event_types() {
952        let event = SubscriptionEvent::TripleAdded {
953            subject: "http://example.org/person1".to_string(),
954            predicate: "http://xmlns.com/foaf/0.1/name".to_string(),
955            object: "John Doe".to_string(),
956        };
957
958        matches!(event, SubscriptionEvent::TripleAdded { .. });
959    }
960
961    #[test]
962    fn test_active_subscription() {
963        let subscription = ActiveSubscription {
964            id: "sub1".to_string(),
965            connection_id: "conn1".to_string(),
966            document: crate::ast::Document {
967                definitions: vec![],
968            },
969            context: ExecutionContext::new(),
970            created_at: Instant::now(),
971            last_execution: None,
972            execution_count: 0,
973        };
974
975        assert_eq!(subscription.id, "sub1");
976        assert_eq!(subscription.connection_id, "conn1");
977        assert_eq!(subscription.execution_count, 0);
978    }
979
980    /// Regression test: a panic while a writer holds `active_subscriptions`
981    /// must not poison the lock for the rest of the manager's lifetime.
982    /// This previously used `std::sync::RwLock` with `.expect("lock poisoned")`,
983    /// so a single panicking writer would cascade into every subsequent
984    /// subscribe/unsubscribe/broadcast call panicking too. The manager now
985    /// uses `tokio::sync::RwLock`, which has no poisoning concept at all.
986    #[tokio::test]
987    async fn test_lock_not_poisoned_after_writer_panic() {
988        let config = SubscriptionConfig::default();
989        let schema = create_test_schema();
990        let executor = QueryExecutor::new(schema.clone());
991        let manager = Arc::new(SubscriptionManager::new(config, schema, executor));
992
993        // Spawn a task that panics while holding the write lock.
994        let manager_clone = Arc::clone(&manager);
995        let handle = tokio::spawn(async move {
996            let _guard = manager_clone.active_subscriptions.write().await;
997            panic!("simulated writer panic while holding the lock");
998        });
999        // The panic is expected; the important assertion is what happens next.
1000        assert!(handle.await.is_err());
1001
1002        // The manager must still be fully usable: no poisoning propagates.
1003        let stats = manager.get_stats().await;
1004        assert_eq!(stats.total_subscriptions, 0);
1005
1006        {
1007            let mut active = manager.active_subscriptions.write().await;
1008            active.insert(
1009                "sub-after-panic".to_string(),
1010                ActiveSubscription {
1011                    id: "sub-after-panic".to_string(),
1012                    connection_id: "conn-after-panic".to_string(),
1013                    document: crate::ast::Document {
1014                        definitions: vec![],
1015                    },
1016                    context: ExecutionContext::new(),
1017                    created_at: Instant::now(),
1018                    last_execution: None,
1019                    execution_count: 0,
1020                },
1021            );
1022        }
1023
1024        let stats = manager.get_stats().await;
1025        assert_eq!(stats.total_subscriptions, 1);
1026    }
1027}