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}