Skip to main content

reinhardt_graphql/
context.rs

1//! GraphQL context for request-scoped data
2//!
3//! Provides context management for GraphQL query execution, including
4//! request information, user authentication, data loaders, and custom data.
5
6use async_trait::async_trait;
7use serde_json::Value;
8use std::any::{Any, TypeId};
9use std::collections::HashMap;
10use std::sync::Arc;
11use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
12use tracing::warn;
13
14/// Error types for context operations
15#[derive(Debug, thiserror::Error)]
16pub enum ContextError {
17	/// Required data was not found in context
18	#[error("Required context data not found for key: {0}")]
19	DataNotFound(String),
20	/// Required data loader was not found in context
21	#[error("Required data loader not found: {0}")]
22	LoaderNotFound(String),
23}
24
25/// Error types for data loader operations
26#[derive(Debug, thiserror::Error)]
27pub enum LoaderError {
28	/// An error occurred during data loading.
29	#[error("Loader error: {0}")]
30	Load(String),
31	/// The requested item was not found.
32	#[error("Not found: {0}")]
33	NotFound(String),
34	/// The loaded data was invalid or could not be parsed.
35	#[error("Invalid data: {0}")]
36	InvalidData(String),
37}
38
39/// Trait for implementing data loaders
40///
41/// Data loaders provide batching and caching for database queries,
42/// helping to solve the N+1 query problem in GraphQL.
43///
44/// # Examples
45///
46/// ```
47/// use reinhardt_graphql::context::{DataLoader, LoaderError};
48/// use async_trait::async_trait;
49///
50/// struct UserLoader;
51///
52/// #[async_trait]
53/// impl DataLoader for UserLoader {
54///     type Key = String;
55///     type Value = String;
56///
57///     async fn load(&self, key: Self::Key) -> Result<Self::Value, LoaderError> {
58///         Ok(format!("User: {}", key))
59///     }
60///
61///     async fn load_many(&self, keys: Vec<Self::Key>) -> Result<Vec<Self::Value>, LoaderError> {
62///         Ok(keys.into_iter().map(|k| format!("User: {}", k)).collect())
63///     }
64/// }
65/// ```
66#[async_trait]
67pub trait DataLoader: Send + Sync + 'static {
68	/// The key type used to look up values.
69	type Key: Send;
70	/// The value type returned by the loader.
71	type Value: Send;
72
73	/// Load a single value by key
74	async fn load(&self, key: Self::Key) -> Result<Self::Value, LoaderError>;
75
76	/// Load multiple values by keys (batch loading)
77	async fn load_many(&self, keys: Vec<Self::Key>) -> Result<Vec<Self::Value>, LoaderError>;
78}
79
80/// GraphQL context for managing request-scoped data
81///
82/// Provides access to request information, user authentication state,
83/// data loaders for efficient batch loading, and custom data storage.
84///
85/// # Examples
86///
87/// ```
88/// use reinhardt_graphql::context::GraphQLContext;
89/// use serde_json::json;
90///
91/// let context = GraphQLContext::new();
92///
93/// // Set custom data
94/// context.set_data("api_version".to_string(), json!("v1"));
95///
96/// // Get custom data
97/// let version = context.get_data("api_version");
98/// assert_eq!(version, Some(json!("v1")));
99/// ```
100pub struct GraphQLContext {
101	/// Custom data storage
102	custom_data: Arc<RwLock<HashMap<String, Value>>>,
103	/// Type-erased data loaders
104	data_loaders: Arc<RwLock<HashMap<TypeId, Box<dyn Any + Send + Sync>>>>,
105}
106
107impl GraphQLContext {
108	/// Create a new GraphQL context
109	///
110	/// # Examples
111	///
112	/// ```
113	/// use reinhardt_graphql::context::GraphQLContext;
114	///
115	/// let context = GraphQLContext::new();
116	/// assert!(context.get_data("nonexistent").is_none());
117	/// ```
118	pub fn new() -> Self {
119		Self {
120			custom_data: Arc::new(RwLock::new(HashMap::new())),
121			data_loaders: Arc::new(RwLock::new(HashMap::new())),
122		}
123	}
124
125	/// Acquires a read lock on custom_data with poison recovery.
126	fn custom_data_read(&self) -> RwLockReadGuard<'_, HashMap<String, Value>> {
127		self.custom_data.read().unwrap_or_else(|e| {
128			warn!("custom_data RwLock was poisoned, recovering read lock");
129			e.into_inner()
130		})
131	}
132
133	/// Acquires a write lock on custom_data with poison recovery.
134	fn custom_data_write(&self) -> RwLockWriteGuard<'_, HashMap<String, Value>> {
135		self.custom_data.write().unwrap_or_else(|e| {
136			warn!("custom_data RwLock was poisoned, recovering write lock");
137			e.into_inner()
138		})
139	}
140
141	/// Acquires a read lock on data_loaders with poison recovery.
142	fn loaders_read(&self) -> RwLockReadGuard<'_, HashMap<TypeId, Box<dyn Any + Send + Sync>>> {
143		self.data_loaders.read().unwrap_or_else(|e| {
144			warn!("data_loaders RwLock was poisoned, recovering read lock");
145			e.into_inner()
146		})
147	}
148
149	/// Acquires a write lock on data_loaders with poison recovery.
150	fn loaders_write(&self) -> RwLockWriteGuard<'_, HashMap<TypeId, Box<dyn Any + Send + Sync>>> {
151		self.data_loaders.write().unwrap_or_else(|e| {
152			warn!("data_loaders RwLock was poisoned, recovering write lock");
153			e.into_inner()
154		})
155	}
156
157	/// Set custom data in the context
158	///
159	/// # Examples
160	///
161	/// ```
162	/// use reinhardt_graphql::context::GraphQLContext;
163	/// use serde_json::json;
164	///
165	/// let context = GraphQLContext::new();
166	/// context.set_data("user_id".to_string(), json!("123"));
167	///
168	/// assert_eq!(context.get_data("user_id"), Some(json!("123")));
169	/// ```
170	pub fn set_data(&self, key: String, value: Value) {
171		let mut data = self.custom_data_write();
172		data.insert(key, value);
173	}
174
175	/// Get custom data from the context
176	///
177	/// Returns `None` if the key does not exist. For GraphQL resolvers that
178	/// require the data to be present, use [`require_data`](Self::require_data)
179	/// instead to get a proper GraphQL error.
180	///
181	/// # Examples
182	///
183	/// ```
184	/// use reinhardt_graphql::context::GraphQLContext;
185	/// use serde_json::json;
186	///
187	/// let context = GraphQLContext::new();
188	/// context.set_data("count".to_string(), json!(42));
189	///
190	/// assert_eq!(context.get_data("count"), Some(json!(42)));
191	/// assert_eq!(context.get_data("nonexistent"), None);
192	/// ```
193	pub fn get_data(&self, key: &str) -> Option<Value> {
194		let data = self.custom_data_read();
195		data.get(key).cloned()
196	}
197
198	/// Get required custom data from the context, returning a GraphQL error if missing
199	///
200	/// This method should be used in GraphQL resolvers where the data is expected
201	/// to be present. Instead of panicking on a missing key, it returns a
202	/// descriptive [`async_graphql::Error`] that will be surfaced as a GraphQL error
203	/// in the response.
204	///
205	/// # Examples
206	///
207	/// ```
208	/// use reinhardt_graphql::context::GraphQLContext;
209	/// use serde_json::json;
210	///
211	/// let context = GraphQLContext::new();
212	/// context.set_data("api_version".to_string(), json!("v1"));
213	///
214	/// // Successful lookup
215	/// let version = context.require_data("api_version");
216	/// assert!(version.is_ok());
217	/// assert_eq!(version.unwrap(), json!("v1"));
218	///
219	/// // Missing key returns an error
220	/// let missing = context.require_data("nonexistent");
221	/// assert!(missing.is_err());
222	/// ```
223	pub fn require_data(&self, key: &str) -> async_graphql::Result<Value> {
224		self.get_data(key)
225			.ok_or_else(|| ContextError::DataNotFound(key.to_string()).into())
226	}
227
228	/// Remove custom data from the context
229	///
230	/// # Examples
231	///
232	/// ```
233	/// use reinhardt_graphql::context::GraphQLContext;
234	/// use serde_json::json;
235	///
236	/// let context = GraphQLContext::new();
237	/// context.set_data("temp".to_string(), json!("value"));
238	///
239	/// let removed = context.remove_data("temp");
240	/// assert_eq!(removed, Some(json!("value")));
241	/// assert_eq!(context.get_data("temp"), None);
242	/// ```
243	pub fn remove_data(&self, key: &str) -> Option<Value> {
244		let mut data = self.custom_data_write();
245		data.remove(key)
246	}
247
248	/// Clear all custom data
249	///
250	/// # Examples
251	///
252	/// ```
253	/// use reinhardt_graphql::context::GraphQLContext;
254	/// use serde_json::json;
255	///
256	/// let context = GraphQLContext::new();
257	/// context.set_data("key1".to_string(), json!("value1"));
258	/// context.set_data("key2".to_string(), json!("value2"));
259	///
260	/// context.clear_data();
261	///
262	/// assert_eq!(context.get_data("key1"), None);
263	/// assert_eq!(context.get_data("key2"), None);
264	/// ```
265	pub fn clear_data(&self) {
266		let mut data = self.custom_data_write();
267		data.clear();
268	}
269
270	/// Add a data loader to the context
271	///
272	/// # Examples
273	///
274	/// ```
275	/// use reinhardt_graphql::context::{GraphQLContext, DataLoader, LoaderError};
276	/// use async_trait::async_trait;
277	/// use std::sync::Arc;
278	///
279	/// struct SimpleLoader;
280	///
281	/// #[async_trait]
282	/// impl DataLoader for SimpleLoader {
283	///     type Key = i32;
284	///     type Value = String;
285	///
286	///     async fn load(&self, key: Self::Key) -> Result<Self::Value, LoaderError> {
287	///         Ok(format!("Value {}", key))
288	///     }
289	///
290	///     async fn load_many(&self, keys: Vec<Self::Key>) -> Result<Vec<Self::Value>, LoaderError> {
291	///         Ok(keys.iter().map(|k| format!("Value {}", k)).collect())
292	///     }
293	/// }
294	///
295	/// let context = GraphQLContext::new();
296	/// let loader = Arc::new(SimpleLoader);
297	/// context.add_data_loader(loader.clone());
298	///
299	/// let retrieved = context.get_data_loader::<SimpleLoader>();
300	/// assert!(retrieved.is_some());
301	/// ```
302	pub fn add_data_loader<T: DataLoader>(&self, loader: Arc<T>) {
303		let mut loaders = self.loaders_write();
304		loaders.insert(TypeId::of::<T>(), Box::new(loader));
305	}
306
307	/// Get a data loader from the context
308	///
309	/// Returns `None` if the loader has not been registered. For GraphQL
310	/// resolvers that require the loader, use
311	/// [`require_data_loader`](Self::require_data_loader) instead to get a
312	/// proper GraphQL error.
313	///
314	/// # Examples
315	///
316	/// ```
317	/// use reinhardt_graphql::context::{GraphQLContext, DataLoader, LoaderError};
318	/// use async_trait::async_trait;
319	/// use std::sync::Arc;
320	///
321	/// struct TestLoader;
322	///
323	/// #[async_trait]
324	/// impl DataLoader for TestLoader {
325	///     type Key = String;
326	///     type Value = i32;
327	///
328	///     async fn load(&self, _key: Self::Key) -> Result<Self::Value, LoaderError> {
329	///         Ok(42)
330	///     }
331	///
332	///     async fn load_many(&self, keys: Vec<Self::Key>) -> Result<Vec<Self::Value>, LoaderError> {
333	///         Ok(vec![42; keys.len()])
334	///     }
335	/// }
336	///
337	/// let context = GraphQLContext::new();
338	/// let loader = Arc::new(TestLoader);
339	/// context.add_data_loader(loader);
340	///
341	/// let retrieved = context.get_data_loader::<TestLoader>();
342	/// assert!(retrieved.is_some());
343	/// ```
344	pub fn get_data_loader<T: DataLoader>(&self) -> Option<Arc<T>> {
345		let loaders = self.loaders_read();
346		loaders
347			.get(&TypeId::of::<T>())
348			.and_then(|loader| loader.downcast_ref::<Arc<T>>().cloned())
349	}
350
351	/// Get a required data loader from the context, returning a GraphQL error if missing
352	///
353	/// This method should be used in GraphQL resolvers where the data loader is
354	/// expected to be registered. Instead of panicking on a missing loader, it
355	/// returns a descriptive [`async_graphql::Error`] that will be surfaced as a
356	/// GraphQL error in the response.
357	///
358	/// # Examples
359	///
360	/// ```
361	/// use reinhardt_graphql::context::{GraphQLContext, DataLoader, LoaderError};
362	/// use async_trait::async_trait;
363	/// use std::sync::Arc;
364	///
365	/// struct MyLoader;
366	///
367	/// #[async_trait]
368	/// impl DataLoader for MyLoader {
369	///     type Key = String;
370	///     type Value = i32;
371	///
372	///     async fn load(&self, _key: Self::Key) -> Result<Self::Value, LoaderError> {
373	///         Ok(42)
374	///     }
375	///
376	///     async fn load_many(&self, keys: Vec<Self::Key>) -> Result<Vec<Self::Value>, LoaderError> {
377	///         Ok(vec![42; keys.len()])
378	///     }
379	/// }
380	///
381	/// let context = GraphQLContext::new();
382	///
383	/// // Missing loader returns an error
384	/// let result = context.require_data_loader::<MyLoader>();
385	/// assert!(result.is_err());
386	///
387	/// // After adding the loader, it succeeds
388	/// context.add_data_loader(Arc::new(MyLoader));
389	/// let result = context.require_data_loader::<MyLoader>();
390	/// assert!(result.is_ok());
391	/// ```
392	pub fn require_data_loader<T: DataLoader>(&self) -> async_graphql::Result<Arc<T>> {
393		self.get_data_loader::<T>().ok_or_else(|| {
394			ContextError::LoaderNotFound(std::any::type_name::<T>().to_string()).into()
395		})
396	}
397
398	/// Remove a data loader from the context
399	///
400	/// # Examples
401	///
402	/// ```
403	/// use reinhardt_graphql::context::{GraphQLContext, DataLoader, LoaderError};
404	/// use async_trait::async_trait;
405	/// use std::sync::Arc;
406	///
407	/// struct RemovableLoader;
408	///
409	/// #[async_trait]
410	/// impl DataLoader for RemovableLoader {
411	///     type Key = u64;
412	///     type Value = String;
413	///
414	///     async fn load(&self, key: Self::Key) -> Result<Self::Value, LoaderError> {
415	///         Ok(key.to_string())
416	///     }
417	///
418	///     async fn load_many(&self, keys: Vec<Self::Key>) -> Result<Vec<Self::Value>, LoaderError> {
419	///         Ok(keys.iter().map(|k| k.to_string()).collect())
420	///     }
421	/// }
422	///
423	/// let context = GraphQLContext::new();
424	/// let loader = Arc::new(RemovableLoader);
425	/// context.add_data_loader(loader);
426	///
427	/// context.remove_data_loader::<RemovableLoader>();
428	/// assert!(context.get_data_loader::<RemovableLoader>().is_none());
429	/// ```
430	pub fn remove_data_loader<T: DataLoader>(&self) {
431		let mut loaders = self.loaders_write();
432		loaders.remove(&TypeId::of::<T>());
433	}
434
435	/// Clear all data loaders
436	///
437	/// # Examples
438	///
439	/// ```
440	/// use reinhardt_graphql::context::{GraphQLContext, DataLoader, LoaderError};
441	/// use async_trait::async_trait;
442	/// use std::sync::Arc;
443	///
444	/// struct Loader1;
445	/// struct Loader2;
446	///
447	/// #[async_trait]
448	/// impl DataLoader for Loader1 {
449	///     type Key = i32;
450	///     type Value = String;
451	///     async fn load(&self, key: Self::Key) -> Result<Self::Value, LoaderError> {
452	///         Ok(key.to_string())
453	///     }
454	///     async fn load_many(&self, keys: Vec<Self::Key>) -> Result<Vec<Self::Value>, LoaderError> {
455	///         Ok(keys.iter().map(|k| k.to_string()).collect())
456	///     }
457	/// }
458	///
459	/// #[async_trait]
460	/// impl DataLoader for Loader2 {
461	///     type Key = String;
462	///     type Value = i32;
463	///     async fn load(&self, _key: Self::Key) -> Result<Self::Value, LoaderError> {
464	///         Ok(0)
465	///     }
466	///     async fn load_many(&self, keys: Vec<Self::Key>) -> Result<Vec<Self::Value>, LoaderError> {
467	///         Ok(vec![0; keys.len()])
468	///     }
469	/// }
470	///
471	/// let context = GraphQLContext::new();
472	/// context.add_data_loader(Arc::new(Loader1));
473	/// context.add_data_loader(Arc::new(Loader2));
474	///
475	/// context.clear_loaders();
476	///
477	/// assert!(context.get_data_loader::<Loader1>().is_none());
478	/// assert!(context.get_data_loader::<Loader2>().is_none());
479	/// ```
480	pub fn clear_loaders(&self) {
481		let mut loaders = self.loaders_write();
482		loaders.clear();
483	}
484}
485
486impl Default for GraphQLContext {
487	fn default() -> Self {
488		Self::new()
489	}
490}
491
492#[cfg(test)]
493mod tests {
494	use super::*;
495	use rstest::rstest;
496
497	#[derive(Debug)]
498	struct TestLoader;
499
500	#[async_trait]
501	impl DataLoader for TestLoader {
502		type Key = String;
503		type Value = i32;
504
505		async fn load(&self, key: Self::Key) -> Result<Self::Value, LoaderError> {
506			key.parse::<i32>()
507				.map_err(|e| LoaderError::InvalidData(e.to_string()))
508		}
509
510		async fn load_many(&self, keys: Vec<Self::Key>) -> Result<Vec<Self::Value>, LoaderError> {
511			keys.into_iter()
512				.map(|k| {
513					k.parse::<i32>()
514						.map_err(|e| LoaderError::InvalidData(e.to_string()))
515				})
516				.collect()
517		}
518	}
519
520	#[rstest]
521	fn test_context_new() {
522		// Arrange & Act
523		let context = GraphQLContext::new();
524
525		// Assert
526		assert!(context.get_data("any_key").is_none());
527	}
528
529	#[rstest]
530	fn test_set_and_get_data() {
531		// Arrange
532		let context = GraphQLContext::new();
533		let value = serde_json::json!({"name": "test", "value": 42});
534
535		// Act
536		context.set_data("test_key".to_string(), value.clone());
537
538		// Assert
539		let retrieved = context.get_data("test_key");
540		assert_eq!(retrieved, Some(value));
541	}
542
543	#[rstest]
544	fn test_get_nonexistent_data() {
545		// Arrange
546		let context = GraphQLContext::new();
547
548		// Act
549		let result = context.get_data("nonexistent");
550
551		// Assert
552		assert_eq!(result, None);
553	}
554
555	#[rstest]
556	fn test_remove_data() {
557		// Arrange
558		let context = GraphQLContext::new();
559		let value = serde_json::json!("test_value");
560		context.set_data("key".to_string(), value.clone());
561
562		// Act
563		let removed = context.remove_data("key");
564
565		// Assert
566		assert_eq!(removed, Some(value));
567		assert_eq!(context.get_data("key"), None);
568	}
569
570	#[rstest]
571	fn test_clear_data() {
572		// Arrange
573		let context = GraphQLContext::new();
574		context.set_data("key1".to_string(), serde_json::json!(1));
575		context.set_data("key2".to_string(), serde_json::json!(2));
576		context.set_data("key3".to_string(), serde_json::json!(3));
577
578		// Act
579		context.clear_data();
580
581		// Assert
582		assert_eq!(context.get_data("key1"), None);
583		assert_eq!(context.get_data("key2"), None);
584		assert_eq!(context.get_data("key3"), None);
585	}
586
587	#[rstest]
588	fn test_add_and_get_data_loader() {
589		// Arrange
590		let context = GraphQLContext::new();
591		let loader = Arc::new(TestLoader);
592
593		// Act
594		context.add_data_loader(loader);
595
596		// Assert
597		let retrieved = context.get_data_loader::<TestLoader>();
598		assert!(retrieved.is_some());
599	}
600
601	#[rstest]
602	fn test_get_nonexistent_loader() {
603		// Arrange
604		let context = GraphQLContext::new();
605
606		// Act
607		let result = context.get_data_loader::<TestLoader>();
608
609		// Assert
610		assert!(result.is_none());
611	}
612
613	#[rstest]
614	fn test_remove_data_loader() {
615		// Arrange
616		let context = GraphQLContext::new();
617		let loader = Arc::new(TestLoader);
618		context.add_data_loader(loader);
619
620		// Act
621		context.remove_data_loader::<TestLoader>();
622
623		// Assert
624		let result = context.get_data_loader::<TestLoader>();
625		assert!(result.is_none());
626	}
627
628	#[rstest]
629	fn test_clear_loaders() {
630		struct Loader1;
631		struct Loader2;
632
633		#[async_trait]
634		impl DataLoader for Loader1 {
635			type Key = i32;
636			type Value = String;
637			async fn load(&self, key: Self::Key) -> Result<Self::Value, LoaderError> {
638				Ok(key.to_string())
639			}
640			async fn load_many(
641				&self,
642				keys: Vec<Self::Key>,
643			) -> Result<Vec<Self::Value>, LoaderError> {
644				Ok(keys.iter().map(|k| k.to_string()).collect())
645			}
646		}
647
648		#[async_trait]
649		impl DataLoader for Loader2 {
650			type Key = String;
651			type Value = i32;
652			async fn load(&self, _key: Self::Key) -> Result<Self::Value, LoaderError> {
653				Ok(0)
654			}
655			async fn load_many(
656				&self,
657				keys: Vec<Self::Key>,
658			) -> Result<Vec<Self::Value>, LoaderError> {
659				Ok(vec![0; keys.len()])
660			}
661		}
662
663		// Arrange
664		let context = GraphQLContext::new();
665		context.add_data_loader(Arc::new(Loader1));
666		context.add_data_loader(Arc::new(Loader2));
667
668		// Act
669		context.clear_loaders();
670
671		// Assert
672		assert!(context.get_data_loader::<Loader1>().is_none());
673		assert!(context.get_data_loader::<Loader2>().is_none());
674	}
675
676	#[rstest]
677	fn test_multiple_data_values() {
678		// Arrange
679		let context = GraphQLContext::new();
680
681		// Act
682		context.set_data("int".to_string(), serde_json::json!(123));
683		context.set_data("string".to_string(), serde_json::json!("hello"));
684		context.set_data("array".to_string(), serde_json::json!([1, 2, 3]));
685		context.set_data("object".to_string(), serde_json::json!({"key": "value"}));
686
687		// Assert
688		assert_eq!(context.get_data("int"), Some(serde_json::json!(123)));
689		assert_eq!(context.get_data("string"), Some(serde_json::json!("hello")));
690		assert_eq!(
691			context.get_data("array"),
692			Some(serde_json::json!([1, 2, 3]))
693		);
694		assert_eq!(
695			context.get_data("object"),
696			Some(serde_json::json!({"key": "value"}))
697		);
698	}
699
700	#[rstest]
701	#[tokio::test]
702	async fn test_data_loader_load() {
703		// Arrange
704		let loader = TestLoader;
705
706		// Act
707		let result = loader.load("42".to_string()).await;
708
709		// Assert
710		assert!(result.is_ok());
711		assert_eq!(result.unwrap(), 42);
712	}
713
714	#[rstest]
715	#[tokio::test]
716	async fn test_data_loader_load_many() {
717		// Arrange
718		let loader = TestLoader;
719		let keys = vec!["1".to_string(), "2".to_string(), "3".to_string()];
720
721		// Act
722		let result = loader.load_many(keys).await;
723
724		// Assert
725		assert!(result.is_ok());
726		assert_eq!(result.unwrap(), vec![1, 2, 3]);
727	}
728
729	#[rstest]
730	#[tokio::test]
731	async fn test_data_loader_error() {
732		// Arrange
733		let loader = TestLoader;
734
735		// Act
736		let result = loader.load("invalid".to_string()).await;
737
738		// Assert
739		assert!(result.is_err());
740		match result {
741			Err(LoaderError::InvalidData(_)) => {}
742			_ => panic!("Expected InvalidData error"),
743		}
744	}
745
746	#[rstest]
747	fn test_context_default() {
748		// Arrange & Act
749		let context = GraphQLContext::default();
750
751		// Assert
752		assert!(context.get_data("any_key").is_none());
753	}
754
755	#[rstest]
756	fn test_overwrite_data() {
757		// Arrange
758		let context = GraphQLContext::new();
759		context.set_data("key".to_string(), serde_json::json!(1));
760
761		// Act
762		context.set_data("key".to_string(), serde_json::json!(2));
763
764		// Assert
765		assert_eq!(context.get_data("key"), Some(serde_json::json!(2)));
766	}
767
768	#[rstest]
769	fn test_require_data_returns_value_when_present() {
770		// Arrange
771		let context = GraphQLContext::new();
772		context.set_data("user_id".to_string(), serde_json::json!("user-42"));
773
774		// Act
775		let result = context.require_data("user_id");
776
777		// Assert
778		assert!(result.is_ok());
779		assert_eq!(result.unwrap(), serde_json::json!("user-42"));
780	}
781
782	#[rstest]
783	fn test_require_data_returns_error_when_missing() {
784		// Arrange
785		let context = GraphQLContext::new();
786
787		// Act
788		let result = context.require_data("nonexistent_key");
789
790		// Assert
791		assert!(result.is_err());
792		let err = result.unwrap_err();
793		assert!(
794			err.message.contains("nonexistent_key"),
795			"Error should mention the missing key, got: {}",
796			err.message
797		);
798		assert!(
799			err.message.contains("Required context data not found"),
800			"Error should describe the issue, got: {}",
801			err.message
802		);
803	}
804
805	#[rstest]
806	fn test_require_data_does_not_panic_on_missing_key() {
807		// Arrange
808		let context = GraphQLContext::new();
809
810		// Act -- this must NOT panic, unlike unwrap() on get_data()
811		let result = context.require_data("missing");
812
813		// Assert
814		assert!(result.is_err());
815	}
816
817	#[rstest]
818	fn test_require_data_loader_returns_loader_when_present() {
819		// Arrange
820		let context = GraphQLContext::new();
821		context.add_data_loader(Arc::new(TestLoader));
822
823		// Act
824		let result = context.require_data_loader::<TestLoader>();
825
826		// Assert
827		assert!(result.is_ok());
828	}
829
830	#[rstest]
831	fn test_require_data_loader_returns_error_when_missing() {
832		// Arrange
833		let context = GraphQLContext::new();
834
835		// Act
836		let result = context.require_data_loader::<TestLoader>();
837
838		// Assert
839		assert!(result.is_err());
840		let err = result.unwrap_err();
841		assert!(
842			err.message.contains("Required data loader not found"),
843			"Error should describe the issue, got: {}",
844			err.message
845		);
846	}
847
848	#[rstest]
849	fn test_require_data_loader_does_not_panic_on_missing_loader() {
850		// Arrange
851		let context = GraphQLContext::new();
852
853		// Act -- this must NOT panic, unlike unwrap() on get_data_loader()
854		let result = context.require_data_loader::<TestLoader>();
855
856		// Assert
857		assert!(result.is_err());
858	}
859
860	#[rstest]
861	fn test_require_data_loader_after_removal_returns_error() {
862		// Arrange
863		let context = GraphQLContext::new();
864		context.add_data_loader(Arc::new(TestLoader));
865		context.remove_data_loader::<TestLoader>();
866
867		// Act
868		let result = context.require_data_loader::<TestLoader>();
869
870		// Assert
871		assert!(result.is_err());
872	}
873
874	#[rstest]
875	fn test_context_error_data_not_found_display() {
876		// Arrange
877		let err = ContextError::DataNotFound("my_key".to_string());
878
879		// Act
880		let message = err.to_string();
881
882		// Assert
883		assert_eq!(message, "Required context data not found for key: my_key");
884	}
885
886	#[rstest]
887	fn test_context_error_loader_not_found_display() {
888		// Arrange
889		let err = ContextError::LoaderNotFound("MyLoader".to_string());
890
891		// Act
892		let message = err.to_string();
893
894		// Assert
895		assert_eq!(message, "Required data loader not found: MyLoader");
896	}
897
898	#[rstest]
899	fn test_context_error_converts_to_graphql_error() {
900		// Arrange
901		let err = ContextError::DataNotFound("test_key".to_string());
902
903		// Act
904		let gql_err: async_graphql::Error = err.into();
905
906		// Assert
907		assert!(gql_err.message.contains("test_key"));
908		assert!(gql_err.message.contains("Required context data not found"));
909	}
910}