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}