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