mockforge_graphql/
resolvers.rs

1//! GraphQL resolvers for mock data generation
2
3use async_graphql::Value;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7/// Mock resolver for GraphQL fields
8#[derive(Debug, Clone)]
9pub struct MockResolver {
10    /// Name of the GraphQL field
11    pub field_name: String,
12    /// Type of the GraphQL field (e.g., "String", "Int", "User")
13    pub field_type: String,
14    /// Static mock data value (if no generator)
15    pub mock_data: Value,
16    /// Optional dynamic data generator
17    pub generator: Option<MockDataGenerator>,
18}
19
20/// Data generator for dynamic mock data
21#[derive(Debug, Clone)]
22pub struct MockDataGenerator {
23    /// Type of generator to use
24    pub generator_type: GeneratorType,
25    /// Generator-specific configuration options
26    pub config: HashMap<String, serde_json::Value>,
27}
28
29/// Types of data generators for mock values
30#[derive(Debug, Clone, Serialize, Deserialize)]
31#[serde(tag = "type")]
32pub enum GeneratorType {
33    /// Generate random strings
34    String {
35        /// Minimum string length
36        min_length: usize,
37        /// Maximum string length
38        max_length: usize,
39    },
40    /// Generate random integers
41    Int {
42        /// Minimum integer value
43        min: i64,
44        /// Maximum integer value
45        max: i64,
46    },
47    /// Generate random floats
48    Float {
49        /// Minimum float value
50        min: f64,
51        /// Maximum float value
52        max: f64,
53    },
54    /// Generate UUIDs
55    Uuid,
56    /// Generate email addresses
57    Email,
58    /// Generate names
59    Name,
60    /// Generate timestamps
61    Timestamp,
62    /// Generate from a list of values
63    FromList {
64        /// List of possible values to choose from
65        values: Vec<serde_json::Value>,
66    },
67    /// Generate nested objects
68    Object {
69        /// Map of field names to their generators
70        fields: HashMap<String, Box<GeneratorType>>,
71    },
72    /// Generate arrays
73    Array {
74        /// Generator for array items
75        item_generator: Box<GeneratorType>,
76        /// Minimum array size
77        min_items: usize,
78        /// Maximum array size
79        max_items: usize,
80    },
81}
82
83/// Registry for managing resolvers
84pub struct ResolverRegistry {
85    resolvers: HashMap<String, HashMap<String, MockResolver>>,
86}
87
88impl ResolverRegistry {
89    /// Create a new resolver registry
90    pub fn new() -> Self {
91        Self {
92            resolvers: HashMap::new(),
93        }
94    }
95
96    /// Register a resolver for a specific type and field
97    pub fn register_resolver(&mut self, type_name: &str, resolver: MockResolver) {
98        self.resolvers
99            .entry(type_name.to_string())
100            .or_default()
101            .insert(resolver.field_name.clone(), resolver);
102    }
103
104    /// Get a resolver for a specific type and field
105    pub fn get_resolver(&self, type_name: &str, field_name: &str) -> Option<&MockResolver> {
106        self.resolvers
107            .get(type_name)
108            .and_then(|type_resolvers| type_resolvers.get(field_name))
109    }
110
111    /// Generate mock data for a field
112    pub async fn generate_mock_data(&self, type_name: &str, field_name: &str) -> Value {
113        if let Some(resolver) = self.get_resolver(type_name, field_name) {
114            if let Some(generator) = &resolver.generator {
115                return generator.generate().await;
116            }
117            return resolver.mock_data.clone();
118        }
119
120        // Default mock data generation based on field name patterns
121        Self::generate_default_mock_data(field_name).await
122    }
123
124    /// Generate default mock data based on field name patterns
125    async fn generate_default_mock_data(field_name: &str) -> Value {
126        match field_name.to_lowercase().as_str() {
127            "id" => Value::String(mockforge_core::templating::expand_str("{{uuid}}")),
128            "name" | "title" => {
129                Value::String(mockforge_core::templating::expand_str("{{faker.name}}"))
130            }
131            "email" => Value::String(mockforge_core::templating::expand_str("{{faker.email}}")),
132            "description" | "content" => {
133                Value::String(mockforge_core::templating::expand_str("{{faker.sentence}}"))
134            }
135            "age" | "count" | "quantity" => Value::Number((rand::random::<u32>() % 100).into()),
136            "price" | "amount" => {
137                let val = rand::random::<f64>() * 1000.0;
138                Value::Number(
139                    serde_json::Number::from_f64(val)
140                        .unwrap_or_else(|| serde_json::Number::from(0)),
141                )
142            }
143            "active" | "enabled" | "is_active" => Value::Boolean(rand::random::<bool>()),
144            "created_at" | "updated_at" => {
145                Value::String(mockforge_core::templating::expand_str("{{now}}"))
146            }
147            _ => Value::String(mockforge_core::templating::expand_str("{{faker.word}}")),
148        }
149    }
150
151    /// Create common resolvers for standard GraphQL types
152    pub fn create_common_resolvers() -> Self {
153        let mut registry = Self::new();
154
155        // User type resolvers
156        registry.register_resolver(
157            "User",
158            MockResolver {
159                field_name: "id".to_string(),
160                field_type: "ID!".to_string(),
161                mock_data: Value::Null,
162                generator: Some(MockDataGenerator {
163                    generator_type: GeneratorType::Uuid,
164                    config: HashMap::new(),
165                }),
166            },
167        );
168
169        registry.register_resolver(
170            "User",
171            MockResolver {
172                field_name: "name".to_string(),
173                field_type: "String!".to_string(),
174                mock_data: Value::Null,
175                generator: Some(MockDataGenerator {
176                    generator_type: GeneratorType::Name,
177                    config: HashMap::new(),
178                }),
179            },
180        );
181
182        registry.register_resolver(
183            "User",
184            MockResolver {
185                field_name: "email".to_string(),
186                field_type: "String!".to_string(),
187                mock_data: Value::Null,
188                generator: Some(MockDataGenerator {
189                    generator_type: GeneratorType::Email,
190                    config: HashMap::new(),
191                }),
192            },
193        );
194
195        registry.register_resolver(
196            "User",
197            MockResolver {
198                field_name: "createdAt".to_string(),
199                field_type: "String!".to_string(),
200                mock_data: Value::Null,
201                generator: Some(MockDataGenerator {
202                    generator_type: GeneratorType::Timestamp,
203                    config: HashMap::new(),
204                }),
205            },
206        );
207
208        registry
209    }
210}
211
212impl MockDataGenerator {
213    /// Generate mock data using this generator
214    pub async fn generate(&self) -> Value {
215        match &self.generator_type {
216            GeneratorType::String {
217                min_length,
218                max_length,
219            } => {
220                use rand::Rng;
221                let mut rng = rand::thread_rng();
222                let length = rng.gen_range(*min_length..*max_length);
223                let s: String = (0..length)
224                    .map(|_| {
225                        let c = rng.gen_range(b'a'..=b'z');
226                        c as char
227                    })
228                    .collect();
229                Value::String(s)
230            }
231            GeneratorType::Int { min, max } => {
232                let num = rand::random::<i64>() % (max - min) + min;
233                Value::Number(num.into())
234            }
235            GeneratorType::Float { min, max } => {
236                let num = rand::random::<f64>() * (max - min) + min;
237                Value::Number(
238                    serde_json::Number::from_f64(num)
239                        .unwrap_or_else(|| serde_json::Number::from(0)),
240                )
241            }
242            GeneratorType::Uuid => {
243                Value::String(mockforge_core::templating::expand_str("{{uuid}}"))
244            }
245            GeneratorType::Email => {
246                Value::String(mockforge_core::templating::expand_str("{{faker.email}}"))
247            }
248            GeneratorType::Name => {
249                Value::String(mockforge_core::templating::expand_str("{{faker.name}}"))
250            }
251            GeneratorType::Timestamp => {
252                Value::String(mockforge_core::templating::expand_str("{{now}}"))
253            }
254            GeneratorType::FromList { values } => {
255                use rand::Rng;
256                if values.is_empty() {
257                    Value::Null
258                } else {
259                    let mut rng = rand::thread_rng();
260                    let index = rng.gen_range(0..values.len());
261                    serde_json::from_value(values[index].clone()).unwrap_or(Value::Null)
262                }
263            }
264            GeneratorType::Object { fields: _ } => {
265                // Returns empty object
266                // Note: Nested object generation is intentionally simplified to avoid
267                // recursion issues. For mock testing, an empty object of the correct type
268                // is typically sufficient. Users can implement custom handlers for
269                // complex nested structures if needed.
270                use indexmap::IndexMap;
271                let map = IndexMap::new();
272                Value::Object(map)
273            }
274            GeneratorType::Array {
275                item_generator: _,
276                min_items,
277                max_items,
278            } => {
279                // Returns array of nulls with correct count
280                // Note: Array item generation is intentionally simplified to avoid
281                // recursion issues. The array has the correct length, which is
282                // sufficient for most mock scenarios. Users can implement custom
283                // handlers for arrays with complex items if needed.
284                use rand::Rng;
285                let mut rng = rand::thread_rng();
286                let count = rng.gen_range(*min_items..*max_items);
287                let items = vec![Value::Null; count];
288                Value::List(items)
289            }
290        }
291    }
292}
293
294impl Default for ResolverRegistry {
295    fn default() -> Self {
296        Self::new()
297    }
298}
299
300#[cfg(test)]
301mod tests {
302    use super::*;
303
304    #[test]
305    fn test_mock_resolver_creation() {
306        let resolver = MockResolver {
307            field_name: "id".to_string(),
308            field_type: "ID!".to_string(),
309            mock_data: Value::String("test-123".to_string()),
310            generator: None,
311        };
312
313        assert_eq!(resolver.field_name, "id");
314        assert_eq!(resolver.field_type, "ID!");
315        assert!(resolver.generator.is_none());
316    }
317
318    #[test]
319    fn test_mock_resolver_with_generator() {
320        let resolver = MockResolver {
321            field_name: "email".to_string(),
322            field_type: "String!".to_string(),
323            mock_data: Value::Null,
324            generator: Some(MockDataGenerator {
325                generator_type: GeneratorType::Email,
326                config: HashMap::new(),
327            }),
328        };
329
330        assert!(resolver.generator.is_some());
331    }
332
333    #[test]
334    fn test_generator_type_string() {
335        let gen_type = GeneratorType::String {
336            min_length: 5,
337            max_length: 10,
338        };
339
340        match gen_type {
341            GeneratorType::String {
342                min_length,
343                max_length,
344            } => {
345                assert_eq!(min_length, 5);
346                assert_eq!(max_length, 10);
347            }
348            _ => panic!("Wrong generator type"),
349        }
350    }
351
352    #[test]
353    fn test_generator_type_int() {
354        let gen_type = GeneratorType::Int { min: 1, max: 100 };
355
356        match gen_type {
357            GeneratorType::Int { min, max } => {
358                assert_eq!(min, 1);
359                assert_eq!(max, 100);
360            }
361            _ => panic!("Wrong generator type"),
362        }
363    }
364
365    #[test]
366    fn test_generator_type_float() {
367        let gen_type = GeneratorType::Float { min: 0.0, max: 1.0 };
368
369        match gen_type {
370            GeneratorType::Float { min, max } => {
371                assert_eq!(min, 0.0);
372                assert_eq!(max, 1.0);
373            }
374            _ => panic!("Wrong generator type"),
375        }
376    }
377
378    #[test]
379    fn test_generator_type_uuid() {
380        let gen_type = GeneratorType::Uuid;
381        assert!(matches!(gen_type, GeneratorType::Uuid));
382    }
383
384    #[test]
385    fn test_generator_type_email() {
386        let gen_type = GeneratorType::Email;
387        assert!(matches!(gen_type, GeneratorType::Email));
388    }
389
390    #[test]
391    fn test_generator_type_name() {
392        let gen_type = GeneratorType::Name;
393        assert!(matches!(gen_type, GeneratorType::Name));
394    }
395
396    #[test]
397    fn test_generator_type_timestamp() {
398        let gen_type = GeneratorType::Timestamp;
399        assert!(matches!(gen_type, GeneratorType::Timestamp));
400    }
401
402    #[test]
403    fn test_generator_type_from_list() {
404        let values = vec![serde_json::json!("value1"), serde_json::json!("value2")];
405        let gen_type = GeneratorType::FromList {
406            values: values.clone(),
407        };
408
409        match gen_type {
410            GeneratorType::FromList { values: v } => {
411                assert_eq!(v.len(), 2);
412            }
413            _ => panic!("Wrong generator type"),
414        }
415    }
416
417    #[test]
418    fn test_resolver_registry_new() {
419        let registry = ResolverRegistry::new();
420        assert_eq!(registry.resolvers.len(), 0);
421    }
422
423    #[test]
424    fn test_resolver_registry_default() {
425        let registry = ResolverRegistry::default();
426        assert_eq!(registry.resolvers.len(), 0);
427    }
428
429    #[test]
430    fn test_resolver_registry_register() {
431        let mut registry = ResolverRegistry::new();
432
433        let resolver = MockResolver {
434            field_name: "id".to_string(),
435            field_type: "ID!".to_string(),
436            mock_data: Value::String("test-id".to_string()),
437            generator: None,
438        };
439
440        registry.register_resolver("User", resolver);
441
442        assert!(registry.resolvers.contains_key("User"));
443        assert!(registry.resolvers.get("User").unwrap().contains_key("id"));
444    }
445
446    #[test]
447    fn test_resolver_registry_get_resolver() {
448        let mut registry = ResolverRegistry::new();
449
450        let resolver = MockResolver {
451            field_name: "email".to_string(),
452            field_type: "String!".to_string(),
453            mock_data: Value::String("test@example.com".to_string()),
454            generator: None,
455        };
456
457        registry.register_resolver("User", resolver);
458
459        let retrieved = registry.get_resolver("User", "email");
460        assert!(retrieved.is_some());
461        assert_eq!(retrieved.unwrap().field_name, "email");
462    }
463
464    #[test]
465    fn test_resolver_registry_get_resolver_not_found() {
466        let registry = ResolverRegistry::new();
467        let retrieved = registry.get_resolver("User", "unknown");
468        assert!(retrieved.is_none());
469    }
470
471    #[tokio::test]
472    async fn test_generate_mock_data_with_static_data() {
473        let mut registry = ResolverRegistry::new();
474
475        let resolver = MockResolver {
476            field_name: "name".to_string(),
477            field_type: "String!".to_string(),
478            mock_data: Value::String("John Doe".to_string()),
479            generator: None,
480        };
481
482        registry.register_resolver("User", resolver);
483
484        let result = registry.generate_mock_data("User", "name").await;
485        assert!(matches!(result, Value::String(_)));
486    }
487
488    #[tokio::test]
489    async fn test_generate_default_mock_data_id() {
490        let result = ResolverRegistry::generate_default_mock_data("id").await;
491        assert!(matches!(result, Value::String(_)));
492    }
493
494    #[tokio::test]
495    async fn test_generate_default_mock_data_name() {
496        let result = ResolverRegistry::generate_default_mock_data("name").await;
497        assert!(matches!(result, Value::String(_)));
498    }
499
500    #[tokio::test]
501    async fn test_generate_default_mock_data_email() {
502        let result = ResolverRegistry::generate_default_mock_data("email").await;
503        assert!(matches!(result, Value::String(_)));
504    }
505
506    #[tokio::test]
507    async fn test_generate_default_mock_data_age() {
508        let result = ResolverRegistry::generate_default_mock_data("age").await;
509        assert!(matches!(result, Value::Number(_)));
510    }
511
512    #[tokio::test]
513    async fn test_generate_default_mock_data_active() {
514        let result = ResolverRegistry::generate_default_mock_data("active").await;
515        assert!(matches!(result, Value::Boolean(_)));
516    }
517
518    #[test]
519    fn test_create_common_resolvers() {
520        let registry = ResolverRegistry::create_common_resolvers();
521
522        assert!(registry.get_resolver("User", "id").is_some());
523        assert!(registry.get_resolver("User", "name").is_some());
524        assert!(registry.get_resolver("User", "email").is_some());
525        assert!(registry.get_resolver("User", "createdAt").is_some());
526    }
527
528    #[tokio::test]
529    async fn test_mock_data_generator_uuid() {
530        let generator = MockDataGenerator {
531            generator_type: GeneratorType::Uuid,
532            config: HashMap::new(),
533        };
534
535        let result = generator.generate().await;
536        assert!(matches!(result, Value::String(_)));
537    }
538
539    #[tokio::test]
540    async fn test_mock_data_generator_email() {
541        let generator = MockDataGenerator {
542            generator_type: GeneratorType::Email,
543            config: HashMap::new(),
544        };
545
546        let result = generator.generate().await;
547        assert!(matches!(result, Value::String(_)));
548    }
549
550    #[tokio::test]
551    async fn test_mock_data_generator_name() {
552        let generator = MockDataGenerator {
553            generator_type: GeneratorType::Name,
554            config: HashMap::new(),
555        };
556
557        let result = generator.generate().await;
558        assert!(matches!(result, Value::String(_)));
559    }
560
561    #[tokio::test]
562    async fn test_mock_data_generator_timestamp() {
563        let generator = MockDataGenerator {
564            generator_type: GeneratorType::Timestamp,
565            config: HashMap::new(),
566        };
567
568        let result = generator.generate().await;
569        assert!(matches!(result, Value::String(_)));
570    }
571
572    #[tokio::test]
573    async fn test_mock_data_generator_int() {
574        let generator = MockDataGenerator {
575            generator_type: GeneratorType::Int { min: 1, max: 100 },
576            config: HashMap::new(),
577        };
578
579        let result = generator.generate().await;
580        assert!(matches!(result, Value::Number(_)));
581    }
582
583    #[tokio::test]
584    async fn test_mock_data_generator_float() {
585        let generator = MockDataGenerator {
586            generator_type: GeneratorType::Float {
587                min: 0.0,
588                max: 10.0,
589            },
590            config: HashMap::new(),
591        };
592
593        let result = generator.generate().await;
594        assert!(matches!(result, Value::Number(_)));
595    }
596
597    #[tokio::test]
598    async fn test_mock_data_generator_string() {
599        let generator = MockDataGenerator {
600            generator_type: GeneratorType::String {
601                min_length: 5,
602                max_length: 10,
603            },
604            config: HashMap::new(),
605        };
606
607        let result = generator.generate().await;
608        if let Value::String(s) = result {
609            assert!(s.len() >= 5);
610            assert!(s.len() <= 10);
611        } else {
612            panic!("Expected string value");
613        }
614    }
615
616    #[tokio::test]
617    async fn test_mock_data_generator_from_list() {
618        let values = vec![
619            serde_json::json!("value1"),
620            serde_json::json!("value2"),
621            serde_json::json!("value3"),
622        ];
623
624        let generator = MockDataGenerator {
625            generator_type: GeneratorType::FromList { values },
626            config: HashMap::new(),
627        };
628
629        let result = generator.generate().await;
630        assert!(matches!(result, Value::String(_)));
631    }
632
633    #[tokio::test]
634    async fn test_mock_data_generator_from_empty_list() {
635        let generator = MockDataGenerator {
636            generator_type: GeneratorType::FromList { values: vec![] },
637            config: HashMap::new(),
638        };
639
640        let result = generator.generate().await;
641        assert!(matches!(result, Value::Null));
642    }
643
644    #[tokio::test]
645    async fn test_mock_data_generator_array() {
646        let generator = MockDataGenerator {
647            generator_type: GeneratorType::Array {
648                item_generator: Box::new(GeneratorType::Uuid),
649                min_items: 2,
650                max_items: 5,
651            },
652            config: HashMap::new(),
653        };
654
655        let result = generator.generate().await;
656        if let Value::List(items) = result {
657            assert!(items.len() >= 2);
658            assert!(items.len() <= 5);
659        } else {
660            panic!("Expected list value");
661        }
662    }
663
664    #[tokio::test]
665    async fn test_resolver_registry_multiple_types() {
666        let mut registry = ResolverRegistry::new();
667
668        registry.register_resolver(
669            "User",
670            MockResolver {
671                field_name: "id".to_string(),
672                field_type: "ID!".to_string(),
673                mock_data: Value::String("user-id".to_string()),
674                generator: None,
675            },
676        );
677
678        registry.register_resolver(
679            "Post",
680            MockResolver {
681                field_name: "id".to_string(),
682                field_type: "ID!".to_string(),
683                mock_data: Value::String("post-id".to_string()),
684                generator: None,
685            },
686        );
687
688        assert!(registry.get_resolver("User", "id").is_some());
689        assert!(registry.get_resolver("Post", "id").is_some());
690    }
691
692    #[test]
693    fn test_resolver_registry_multiple_fields_same_type() {
694        let mut registry = ResolverRegistry::new();
695
696        registry.register_resolver(
697            "User",
698            MockResolver {
699                field_name: "id".to_string(),
700                field_type: "ID!".to_string(),
701                mock_data: Value::Null,
702                generator: None,
703            },
704        );
705
706        registry.register_resolver(
707            "User",
708            MockResolver {
709                field_name: "name".to_string(),
710                field_type: "String!".to_string(),
711                mock_data: Value::Null,
712                generator: None,
713            },
714        );
715
716        registry.register_resolver(
717            "User",
718            MockResolver {
719                field_name: "email".to_string(),
720                field_type: "String!".to_string(),
721                mock_data: Value::Null,
722                generator: None,
723            },
724        );
725
726        assert_eq!(registry.resolvers.get("User").unwrap().len(), 3);
727    }
728}