Skip to main content

reinhardt_core/signals/
graphql_integration.rs

1#![cfg(native)]
2
3//! GraphQL Subscriptions - Signal-based GraphQL subscription support
4//!
5//! This module provides GraphQL subscription integration for signals,
6//! allowing signals to drive GraphQL subscription updates.
7//!
8//! # Examples
9//!
10//! ```rust,no_run
11//! use reinhardt_core::signals::graphql_integration::{GraphQLSubscriptionBridge, SubscriptionEvent};
12//! use reinhardt_core::signals::{Signal, SignalName};
13//!
14//! # #[tokio::main]
15//! # async fn main() {
16//! # struct User;
17//! # fn post_save() -> Signal<User> {
18//! #     Signal::new(SignalName::custom("post_save"))
19//! # }
20//! // Create a GraphQL subscription bridge
21//! let bridge = GraphQLSubscriptionBridge::new();
22//!
23//! // Connect a signal to a GraphQL subscription
24//! bridge.connect_signal(
25//!     post_save(),
26//!     "userUpdated",
27//!     |_user| SubscriptionEvent::new("userUpdated", ())
28//! ).await;
29//!
30//! // Subscribe to GraphQL subscription
31//! let _stream = bridge.subscribe("userUpdated").await;
32//! # }
33//! ```
34
35use super::error::SignalError;
36use super::signal::Signal;
37use parking_lot::RwLock;
38use serde::{Deserialize, Serialize, de::DeserializeOwned};
39use std::collections::HashMap;
40use std::fmt;
41use std::marker::PhantomData;
42use std::sync::Arc;
43use tokio::sync::broadcast;
44
45/// GraphQL subscription event
46///
47/// # Examples
48///
49/// ```
50/// use reinhardt_core::signals::graphql_integration::SubscriptionEvent;
51/// use serde_json::json;
52///
53/// let event = SubscriptionEvent::new("userCreated", json!({"id": 1}));
54/// assert_eq!(event.subscription_name, "userCreated");
55/// ```
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct SubscriptionEvent<T> {
58	/// Subscription name
59	pub subscription_name: String,
60	/// Event payload
61	pub data: T,
62	/// Event timestamp (Unix timestamp in milliseconds)
63	pub timestamp: u64,
64}
65
66impl<T> SubscriptionEvent<T> {
67	/// Create a new subscription event
68	///
69	/// # Examples
70	///
71	/// ```
72	/// use reinhardt_core::signals::graphql_integration::SubscriptionEvent;
73	///
74	/// let event = SubscriptionEvent::new("taskCompleted", 42);
75	/// assert_eq!(event.subscription_name, "taskCompleted");
76	/// assert_eq!(event.data, 42);
77	/// ```
78	pub fn new(subscription_name: impl Into<String>, data: T) -> Self {
79		use std::time::{SystemTime, UNIX_EPOCH};
80
81		let timestamp = SystemTime::now()
82			.duration_since(UNIX_EPOCH)
83			.unwrap_or_default()
84			.as_millis() as u64;
85
86		Self {
87			subscription_name: subscription_name.into(),
88			data,
89			timestamp,
90		}
91	}
92}
93
94/// GraphQL subscription stream
95///
96/// Represents an active subscription stream for a specific subscription name
97type SubscriptionStream = broadcast::Sender<String>;
98
99/// GraphQL subscription bridge
100///
101/// Bridges signals to GraphQL subscriptions, allowing signals to trigger
102/// subscription updates
103///
104/// # Examples
105///
106/// ```
107/// use reinhardt_core::signals::graphql_integration::GraphQLSubscriptionBridge;
108///
109/// let bridge = GraphQLSubscriptionBridge::new();
110/// ```
111pub struct GraphQLSubscriptionBridge {
112	streams: Arc<RwLock<HashMap<String, SubscriptionStream>>>,
113}
114
115impl GraphQLSubscriptionBridge {
116	/// Create a new GraphQL subscription bridge
117	///
118	/// # Examples
119	///
120	/// ```
121	/// use reinhardt_core::signals::graphql_integration::GraphQLSubscriptionBridge;
122	///
123	/// let bridge = GraphQLSubscriptionBridge::new();
124	/// ```
125	pub fn new() -> Self {
126		Self {
127			streams: Arc::new(RwLock::new(HashMap::new())),
128		}
129	}
130
131	/// Get or create a subscription stream
132	///
133	/// # Examples
134	///
135	/// ```
136	/// use reinhardt_core::signals::graphql_integration::GraphQLSubscriptionBridge;
137	///
138	/// let bridge = GraphQLSubscriptionBridge::new();
139	/// let stream = bridge.get_or_create_stream("userUpdated");
140	/// ```
141	pub fn get_or_create_stream(&self, subscription_name: &str) -> SubscriptionStream {
142		let mut streams = self.streams.write();
143		streams
144			.entry(subscription_name.to_string())
145			.or_insert_with(|| {
146				let (tx, _) = broadcast::channel(100);
147				tx
148			})
149			.clone()
150	}
151
152	/// Subscribe to a GraphQL subscription
153	///
154	/// # Examples
155	///
156	/// ```
157	/// use reinhardt_core::signals::graphql_integration::GraphQLSubscriptionBridge;
158	///
159	/// # async fn example() {
160	/// let bridge = GraphQLSubscriptionBridge::new();
161	/// let mut receiver = bridge.subscribe("userUpdated").await;
162	/// # }
163	/// ```
164	pub async fn subscribe(
165		&self,
166		subscription_name: impl Into<String>,
167	) -> broadcast::Receiver<String> {
168		let name = subscription_name.into();
169		let stream = self.get_or_create_stream(&name);
170		stream.subscribe()
171	}
172
173	/// Publish an event to a subscription
174	///
175	/// # Examples
176	///
177	/// ```
178	/// use reinhardt_core::signals::graphql_integration::GraphQLSubscriptionBridge;
179	///
180	/// # async fn example() {
181	/// let bridge = GraphQLSubscriptionBridge::new();
182	/// bridge.publish("userUpdated", "{\"id\": 1}".to_string()).await.unwrap();
183	/// # }
184	/// ```
185	pub async fn publish(
186		&self,
187		subscription_name: impl Into<String>,
188		message: String,
189	) -> Result<(), SignalError> {
190		let stream = self.get_or_create_stream(&subscription_name.into());
191		stream
192			.send(message)
193			.map_err(|e| SignalError::new(format!("Failed to publish: {}", e)))?;
194		Ok(())
195	}
196
197	/// Get the number of active subscriptions
198	///
199	/// # Examples
200	///
201	/// ```
202	/// use reinhardt_core::signals::graphql_integration::GraphQLSubscriptionBridge;
203	///
204	/// let bridge = GraphQLSubscriptionBridge::new();
205	/// assert_eq!(bridge.subscription_count(), 0);
206	/// ```
207	pub fn subscription_count(&self) -> usize {
208		self.streams.read().len()
209	}
210
211	/// Get the number of active receivers for a subscription
212	///
213	/// # Examples
214	///
215	/// ```
216	/// use reinhardt_core::signals::graphql_integration::GraphQLSubscriptionBridge;
217	///
218	/// # async fn example() {
219	/// let bridge = GraphQLSubscriptionBridge::new();
220	/// let _receiver = bridge.subscribe("test").await;
221	/// assert_eq!(bridge.receiver_count("test"), 1);
222	/// # }
223	/// ```
224	pub fn receiver_count(&self, subscription_name: &str) -> usize {
225		self.streams
226			.read()
227			.get(subscription_name)
228			.map(|s| s.receiver_count())
229			.unwrap_or(0)
230	}
231
232	/// Connect a signal to a GraphQL subscription
233	///
234	/// When the signal is emitted, it will be transformed and published to the subscription
235	///
236	/// # Examples
237	///
238	/// ```rust,no_run
239	/// use reinhardt_core::signals::graphql_integration::{GraphQLSubscriptionBridge, SubscriptionEvent};
240	/// use reinhardt_core::signals::{Signal, SignalName};
241	///
242	/// # #[tokio::main]
243	/// # async fn main() {
244	/// # use serde::{Serialize, Deserialize};
245	/// # #[derive(Debug, Clone, Serialize, Deserialize)]
246	/// # struct User { id: Option<i64> }
247	/// # fn post_save() -> Signal<User> {
248	/// #     Signal::new(SignalName::custom("post_save"))
249	/// # }
250	/// let bridge = GraphQLSubscriptionBridge::new();
251	/// bridge.connect_signal(
252	///     post_save(),
253	///     "userSaved",
254	///     |_user| SubscriptionEvent::new("userSaved", ())
255	/// ).await;
256	/// # }
257	/// ```
258	pub async fn connect_signal<T, F, E>(
259		&self,
260		signal: Signal<T>,
261		subscription_name: impl Into<String>,
262		transform: F,
263	) where
264		T: Send + Sync + 'static,
265		F: Fn(Arc<T>) -> E + Send + Sync + 'static,
266		E: Serialize + Send + Sync + 'static,
267	{
268		let streams = Arc::clone(&self.streams);
269		let subscription_name = subscription_name.into();
270		let transform = Arc::new(transform);
271
272		signal.connect(move |instance| {
273			let streams = Arc::clone(&streams);
274			let subscription_name = subscription_name.clone();
275			let transform = Arc::clone(&transform);
276
277			async move {
278				let event = transform(instance);
279				let json = serde_json::to_string(&event)
280					.map_err(|e| SignalError::new(format!("Serialization error: {}", e)))?;
281
282				let streams_read = streams.read();
283				if let Some(stream) = streams_read.get(&subscription_name)
284					&& let Err(e) = stream.send(json)
285				{
286					eprintln!("Failed to send GraphQL subscription event: {}", e);
287				}
288
289				Ok(())
290			}
291		});
292	}
293
294	/// Remove a subscription stream
295	///
296	/// # Examples
297	///
298	/// ```
299	/// use reinhardt_core::signals::graphql_integration::GraphQLSubscriptionBridge;
300	///
301	/// let bridge = GraphQLSubscriptionBridge::new();
302	/// bridge.get_or_create_stream("test");
303	/// assert_eq!(bridge.subscription_count(), 1);
304	///
305	/// bridge.remove_stream("test");
306	/// assert_eq!(bridge.subscription_count(), 0);
307	/// ```
308	pub fn remove_stream(&self, subscription_name: &str) {
309		self.streams.write().remove(subscription_name);
310	}
311}
312
313impl Default for GraphQLSubscriptionBridge {
314	fn default() -> Self {
315		Self::new()
316	}
317}
318
319impl Clone for GraphQLSubscriptionBridge {
320	fn clone(&self) -> Self {
321		Self {
322			streams: Arc::clone(&self.streams),
323		}
324	}
325}
326
327impl fmt::Debug for GraphQLSubscriptionBridge {
328	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
329		f.debug_struct("GraphQLSubscriptionBridge")
330			.field("subscription_count", &self.subscription_count())
331			.finish()
332	}
333}
334
335/// Typed GraphQL subscription
336///
337/// A type-safe wrapper for a specific subscription type
338///
339/// # Examples
340///
341/// ```
342/// use reinhardt_core::signals::graphql_integration::{GraphQLSubscriptionBridge, TypedSubscription};
343///
344/// let bridge = GraphQLSubscriptionBridge::new();
345/// let subscription = TypedSubscription::<String>::new(bridge, "stringEvent");
346/// ```
347pub struct TypedSubscription<T>
348where
349	T: Serialize + DeserializeOwned + Send + Sync + 'static,
350{
351	bridge: GraphQLSubscriptionBridge,
352	subscription_name: String,
353	_phantom: PhantomData<T>,
354}
355
356impl<T> TypedSubscription<T>
357where
358	T: Serialize + DeserializeOwned + Send + Sync + 'static,
359{
360	/// Create a new typed subscription
361	///
362	/// # Examples
363	///
364	/// ```
365	/// use reinhardt_core::signals::graphql_integration::{GraphQLSubscriptionBridge, TypedSubscription};
366	///
367	/// let bridge = GraphQLSubscriptionBridge::new();
368	/// let subscription = TypedSubscription::<i32>::new(bridge, "numberEvent");
369	/// ```
370	pub fn new(bridge: GraphQLSubscriptionBridge, subscription_name: impl Into<String>) -> Self {
371		Self {
372			bridge,
373			subscription_name: subscription_name.into(),
374			_phantom: PhantomData,
375		}
376	}
377
378	/// Subscribe and receive typed events
379	///
380	/// # Examples
381	///
382	/// ```rust,no_run
383	/// use reinhardt_core::signals::graphql_integration::{GraphQLSubscriptionBridge, TypedSubscription};
384	///
385	/// # #[tokio::main]
386	/// # async fn main() {
387	/// let bridge = GraphQLSubscriptionBridge::new();
388	/// let subscription = TypedSubscription::<String>::new(bridge, "messages");
389	///
390	/// let mut receiver = subscription.subscribe().await;
391	/// while let Ok(event) = receiver.recv().await {
392	///     println!("Received: {:?}", event);
393	/// }
394	/// # }
395	/// ```
396	pub async fn subscribe(&self) -> broadcast::Receiver<String> {
397		self.bridge.subscribe(&self.subscription_name).await
398	}
399
400	/// Publish a typed event
401	///
402	/// # Examples
403	///
404	/// ```
405	/// use reinhardt_core::signals::graphql_integration::{GraphQLSubscriptionBridge, TypedSubscription, SubscriptionEvent};
406	///
407	/// # async fn example() {
408	/// let bridge = GraphQLSubscriptionBridge::new();
409	/// let subscription = TypedSubscription::new(bridge, "test");
410	///
411	/// let event = SubscriptionEvent::new("test", 42);
412	/// subscription.publish(event).await.unwrap();
413	/// # }
414	/// ```
415	pub async fn publish(&self, event: SubscriptionEvent<T>) -> Result<(), SignalError> {
416		let json = serde_json::to_string(&event)
417			.map_err(|e| SignalError::new(format!("Serialization error: {}", e)))?;
418
419		self.bridge.publish(&self.subscription_name, json).await
420	}
421}
422
423#[cfg(test)]
424mod tests {
425	use super::*;
426
427	#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
428	struct TestData {
429		id: i32,
430		message: String,
431	}
432
433	#[test]
434	fn test_subscription_event_creation() {
435		let event = SubscriptionEvent::new("test", "data");
436		assert_eq!(event.subscription_name, "test");
437		assert_eq!(event.data, "data");
438		assert!(event.timestamp > 0);
439	}
440
441	#[tokio::test]
442	async fn test_graphql_bridge_subscribe() {
443		let bridge = GraphQLSubscriptionBridge::new();
444		let mut receiver = bridge.subscribe("test").await;
445
446		bridge.publish("test", "message".to_string()).await.unwrap();
447
448		let result = receiver.recv().await.unwrap();
449		assert_eq!(result, "message");
450	}
451
452	#[tokio::test]
453	async fn test_graphql_bridge_multiple_subscribers() {
454		let bridge = GraphQLSubscriptionBridge::new();
455		let mut receiver1 = bridge.subscribe("event").await;
456		let mut receiver2 = bridge.subscribe("event").await;
457
458		assert_eq!(bridge.receiver_count("event"), 2);
459
460		bridge.publish("event", "data".to_string()).await.unwrap();
461
462		let msg1 = receiver1.recv().await.unwrap();
463		let msg2 = receiver2.recv().await.unwrap();
464
465		assert_eq!(msg1, "data");
466		assert_eq!(msg2, "data");
467	}
468
469	#[tokio::test]
470	async fn test_graphql_bridge_connect_signal() {
471		let bridge = GraphQLSubscriptionBridge::new();
472		let mut receiver = bridge.subscribe("user_event").await;
473
474		let signal = Signal::<String>::new(crate::signals::SignalName::custom("test"));
475
476		bridge
477			.connect_signal(signal.clone(), "user_event", |data| {
478				SubscriptionEvent::new("user_event", (*data).clone())
479			})
480			.await;
481
482		signal.send("test data".to_string()).await.unwrap();
483
484		let result = receiver.recv().await.unwrap();
485		let parsed: SubscriptionEvent<String> = serde_json::from_str(&result).unwrap();
486		assert_eq!(parsed.subscription_name, "user_event");
487		assert_eq!(parsed.data, "test data");
488	}
489
490	#[tokio::test]
491	async fn test_graphql_bridge_subscription_count() {
492		let bridge = GraphQLSubscriptionBridge::new();
493		assert_eq!(bridge.subscription_count(), 0);
494
495		bridge.get_or_create_stream("sub1");
496		assert_eq!(bridge.subscription_count(), 1);
497
498		bridge.get_or_create_stream("sub2");
499		assert_eq!(bridge.subscription_count(), 2);
500	}
501
502	#[tokio::test]
503	async fn test_graphql_bridge_remove_stream() {
504		let bridge = GraphQLSubscriptionBridge::new();
505
506		bridge.get_or_create_stream("test");
507		assert_eq!(bridge.subscription_count(), 1);
508
509		bridge.remove_stream("test");
510		assert_eq!(bridge.subscription_count(), 0);
511	}
512
513	#[tokio::test]
514	async fn test_typed_subscription() {
515		let bridge = GraphQLSubscriptionBridge::new();
516		let subscription = TypedSubscription::<TestData>::new(bridge, "typed_test");
517
518		let mut receiver = subscription.subscribe().await;
519
520		let event = SubscriptionEvent::new(
521			"typed_test",
522			TestData {
523				id: 1,
524				message: "Hello".to_string(),
525			},
526		);
527
528		subscription.publish(event.clone()).await.unwrap();
529
530		let result = receiver.recv().await.unwrap();
531		let parsed: SubscriptionEvent<TestData> = serde_json::from_str(&result).unwrap();
532
533		assert_eq!(parsed.subscription_name, "typed_test");
534		assert_eq!(parsed.data, event.data);
535	}
536
537	#[tokio::test]
538	async fn test_graphql_bridge_receiver_count() {
539		let bridge = GraphQLSubscriptionBridge::new();
540
541		let _r1 = bridge.subscribe("test").await;
542		assert_eq!(bridge.receiver_count("test"), 1);
543
544		let _r2 = bridge.subscribe("test").await;
545		assert_eq!(bridge.receiver_count("test"), 2);
546
547		drop(_r1);
548		// Note: receiver_count may still show 2 due to async nature of channel cleanup
549	}
550
551	#[tokio::test]
552	async fn test_graphql_bridge_multiple_subscriptions() {
553		let bridge = GraphQLSubscriptionBridge::new();
554
555		let mut r1 = bridge.subscribe("sub1").await;
556		let mut r2 = bridge.subscribe("sub2").await;
557
558		bridge.publish("sub1", "msg1".to_string()).await.unwrap();
559		bridge.publish("sub2", "msg2".to_string()).await.unwrap();
560
561		let result1 = r1.recv().await.unwrap();
562		let result2 = r2.recv().await.unwrap();
563
564		assert_eq!(result1, "msg1");
565		assert_eq!(result2, "msg2");
566	}
567}