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_insert_with(HashMap::new)
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(&format!(
148                "{{{{faker.word}}}}"
149            ))),
150        }
151    }
152
153    /// Create common resolvers for standard GraphQL types
154    pub fn create_common_resolvers() -> Self {
155        let mut registry = Self::new();
156
157        // User type resolvers
158        registry.register_resolver(
159            "User",
160            MockResolver {
161                field_name: "id".to_string(),
162                field_type: "ID!".to_string(),
163                mock_data: Value::Null,
164                generator: Some(MockDataGenerator {
165                    generator_type: GeneratorType::Uuid,
166                    config: HashMap::new(),
167                }),
168            },
169        );
170
171        registry.register_resolver(
172            "User",
173            MockResolver {
174                field_name: "name".to_string(),
175                field_type: "String!".to_string(),
176                mock_data: Value::Null,
177                generator: Some(MockDataGenerator {
178                    generator_type: GeneratorType::Name,
179                    config: HashMap::new(),
180                }),
181            },
182        );
183
184        registry.register_resolver(
185            "User",
186            MockResolver {
187                field_name: "email".to_string(),
188                field_type: "String!".to_string(),
189                mock_data: Value::Null,
190                generator: Some(MockDataGenerator {
191                    generator_type: GeneratorType::Email,
192                    config: HashMap::new(),
193                }),
194            },
195        );
196
197        registry.register_resolver(
198            "User",
199            MockResolver {
200                field_name: "createdAt".to_string(),
201                field_type: "String!".to_string(),
202                mock_data: Value::Null,
203                generator: Some(MockDataGenerator {
204                    generator_type: GeneratorType::Timestamp,
205                    config: HashMap::new(),
206                }),
207            },
208        );
209
210        registry
211    }
212}
213
214impl MockDataGenerator {
215    /// Generate mock data using this generator
216    pub async fn generate(&self) -> Value {
217        match &self.generator_type {
218            GeneratorType::String {
219                min_length,
220                max_length,
221            } => {
222                use rand::Rng;
223                let mut rng = rand::thread_rng();
224                let length = rng.gen_range(*min_length..*max_length);
225                let s: String = (0..length)
226                    .map(|_| {
227                        let c = rng.gen_range(b'a'..=b'z');
228                        c as char
229                    })
230                    .collect();
231                Value::String(s)
232            }
233            GeneratorType::Int { min, max } => {
234                let num = rand::random::<i64>() % (max - min) + min;
235                Value::Number(num.into())
236            }
237            GeneratorType::Float { min, max } => {
238                let num = rand::random::<f64>() * (max - min) + min;
239                Value::Number(
240                    serde_json::Number::from_f64(num)
241                        .unwrap_or_else(|| serde_json::Number::from(0)),
242                )
243            }
244            GeneratorType::Uuid => {
245                Value::String(mockforge_core::templating::expand_str("{{uuid}}"))
246            }
247            GeneratorType::Email => {
248                Value::String(mockforge_core::templating::expand_str("{{faker.email}}"))
249            }
250            GeneratorType::Name => {
251                Value::String(mockforge_core::templating::expand_str("{{faker.name}}"))
252            }
253            GeneratorType::Timestamp => {
254                Value::String(mockforge_core::templating::expand_str("{{now}}"))
255            }
256            GeneratorType::FromList { values } => {
257                use rand::Rng;
258                if values.is_empty() {
259                    Value::Null
260                } else {
261                    let mut rng = rand::thread_rng();
262                    let index = rng.gen_range(0..values.len());
263                    serde_json::from_value(values[index].clone()).unwrap_or(Value::Null)
264                }
265            }
266            GeneratorType::Object { fields: _ } => {
267                // Returns empty object
268                // Note: Nested object generation is intentionally simplified to avoid
269                // recursion issues. For mock testing, an empty object of the correct type
270                // is typically sufficient. Users can implement custom handlers for
271                // complex nested structures if needed.
272                use indexmap::IndexMap;
273                let map = IndexMap::new();
274                Value::Object(map)
275            }
276            GeneratorType::Array {
277                item_generator: _,
278                min_items,
279                max_items,
280            } => {
281                // Returns array of nulls with correct count
282                // Note: Array item generation is intentionally simplified to avoid
283                // recursion issues. The array has the correct length, which is
284                // sufficient for most mock scenarios. Users can implement custom
285                // handlers for arrays with complex items if needed.
286                use rand::Rng;
287                let mut rng = rand::thread_rng();
288                let count = rng.gen_range(*min_items..*max_items);
289                let items = vec![Value::Null; count];
290                Value::List(items)
291            }
292        }
293    }
294}
295
296impl Default for ResolverRegistry {
297    fn default() -> Self {
298        Self::new()
299    }
300}
301
302#[cfg(test)]
303mod tests {
304    use super::*;
305
306    #[test]
307    fn test_mock_resolver_creation() {
308        let resolver = MockResolver {
309            field_name: "id".to_string(),
310            field_type: "ID!".to_string(),
311            mock_data: Value::String("test-123".to_string()),
312            generator: None,
313        };
314
315        assert_eq!(resolver.field_name, "id");
316        assert_eq!(resolver.field_type, "ID!");
317        assert!(resolver.generator.is_none());
318    }
319
320    #[test]
321    fn test_mock_resolver_with_generator() {
322        let resolver = MockResolver {
323            field_name: "email".to_string(),
324            field_type: "String!".to_string(),
325            mock_data: Value::Null,
326            generator: Some(MockDataGenerator {
327                generator_type: GeneratorType::Email,
328                config: HashMap::new(),
329            }),
330        };
331
332        assert!(resolver.generator.is_some());
333    }
334
335    #[test]
336    fn test_generator_type_string() {
337        let gen_type = GeneratorType::String {
338            min_length: 5,
339            max_length: 10,
340        };
341
342        match gen_type {
343            GeneratorType::String {
344                min_length,
345                max_length,
346            } => {
347                assert_eq!(min_length, 5);
348                assert_eq!(max_length, 10);
349            }
350            _ => panic!("Wrong generator type"),
351        }
352    }
353
354    #[test]
355    fn test_generator_type_int() {
356        let gen_type = GeneratorType::Int { min: 1, max: 100 };
357
358        match gen_type {
359            GeneratorType::Int { min, max } => {
360                assert_eq!(min, 1);
361                assert_eq!(max, 100);
362            }
363            _ => panic!("Wrong generator type"),
364        }
365    }
366
367    #[test]
368    fn test_generator_type_float() {
369        let gen_type = GeneratorType::Float { min: 0.0, max: 1.0 };
370
371        match gen_type {
372            GeneratorType::Float { min, max } => {
373                assert_eq!(min, 0.0);
374                assert_eq!(max, 1.0);
375            }
376            _ => panic!("Wrong generator type"),
377        }
378    }
379
380    #[test]
381    fn test_generator_type_uuid() {
382        let gen_type = GeneratorType::Uuid;
383        assert!(matches!(gen_type, GeneratorType::Uuid));
384    }
385
386    #[test]
387    fn test_generator_type_email() {
388        let gen_type = GeneratorType::Email;
389        assert!(matches!(gen_type, GeneratorType::Email));
390    }
391
392    #[test]
393    fn test_generator_type_name() {
394        let gen_type = GeneratorType::Name;
395        assert!(matches!(gen_type, GeneratorType::Name));
396    }
397
398    #[test]
399    fn test_generator_type_timestamp() {
400        let gen_type = GeneratorType::Timestamp;
401        assert!(matches!(gen_type, GeneratorType::Timestamp));
402    }
403
404    #[test]
405    fn test_generator_type_from_list() {
406        let values = vec![serde_json::json!("value1"), serde_json::json!("value2")];
407        let gen_type = GeneratorType::FromList {
408            values: values.clone(),
409        };
410
411        match gen_type {
412            GeneratorType::FromList { values: v } => {
413                assert_eq!(v.len(), 2);
414            }
415            _ => panic!("Wrong generator type"),
416        }
417    }
418
419    #[test]
420    fn test_resolver_registry_new() {
421        let registry = ResolverRegistry::new();
422        assert_eq!(registry.resolvers.len(), 0);
423    }
424
425    #[test]
426    fn test_resolver_registry_default() {
427        let registry = ResolverRegistry::default();
428        assert_eq!(registry.resolvers.len(), 0);
429    }
430
431    #[test]
432    fn test_resolver_registry_register() {
433        let mut registry = ResolverRegistry::new();
434
435        let resolver = MockResolver {
436            field_name: "id".to_string(),
437            field_type: "ID!".to_string(),
438            mock_data: Value::String("test-id".to_string()),
439            generator: None,
440        };
441
442        registry.register_resolver("User", resolver);
443
444        assert!(registry.resolvers.contains_key("User"));
445        assert!(registry.resolvers.get("User").unwrap().contains_key("id"));
446    }
447
448    #[test]
449    fn test_resolver_registry_get_resolver() {
450        let mut registry = ResolverRegistry::new();
451
452        let resolver = MockResolver {
453            field_name: "email".to_string(),
454            field_type: "String!".to_string(),
455            mock_data: Value::String("test@example.com".to_string()),
456            generator: None,
457        };
458
459        registry.register_resolver("User", resolver);
460
461        let retrieved = registry.get_resolver("User", "email");
462        assert!(retrieved.is_some());
463        assert_eq!(retrieved.unwrap().field_name, "email");
464    }
465
466    #[test]
467    fn test_resolver_registry_get_resolver_not_found() {
468        let registry = ResolverRegistry::new();
469        let retrieved = registry.get_resolver("User", "unknown");
470        assert!(retrieved.is_none());
471    }
472
473    #[tokio::test]
474    async fn test_generate_mock_data_with_static_data() {
475        let mut registry = ResolverRegistry::new();
476
477        let resolver = MockResolver {
478            field_name: "name".to_string(),
479            field_type: "String!".to_string(),
480            mock_data: Value::String("John Doe".to_string()),
481            generator: None,
482        };
483
484        registry.register_resolver("User", resolver);
485
486        let result = registry.generate_mock_data("User", "name").await;
487        assert!(matches!(result, Value::String(_)));
488    }
489
490    #[tokio::test]
491    async fn test_generate_default_mock_data_id() {
492        let result = ResolverRegistry::generate_default_mock_data("id").await;
493        assert!(matches!(result, Value::String(_)));
494    }
495
496    #[tokio::test]
497    async fn test_generate_default_mock_data_name() {
498        let result = ResolverRegistry::generate_default_mock_data("name").await;
499        assert!(matches!(result, Value::String(_)));
500    }
501
502    #[tokio::test]
503    async fn test_generate_default_mock_data_email() {
504        let result = ResolverRegistry::generate_default_mock_data("email").await;
505        assert!(matches!(result, Value::String(_)));
506    }
507
508    #[tokio::test]
509    async fn test_generate_default_mock_data_age() {
510        let result = ResolverRegistry::generate_default_mock_data("age").await;
511        assert!(matches!(result, Value::Number(_)));
512    }
513
514    #[tokio::test]
515    async fn test_generate_default_mock_data_active() {
516        let result = ResolverRegistry::generate_default_mock_data("active").await;
517        assert!(matches!(result, Value::Boolean(_)));
518    }
519
520    #[test]
521    fn test_create_common_resolvers() {
522        let registry = ResolverRegistry::create_common_resolvers();
523
524        assert!(registry.get_resolver("User", "id").is_some());
525        assert!(registry.get_resolver("User", "name").is_some());
526        assert!(registry.get_resolver("User", "email").is_some());
527        assert!(registry.get_resolver("User", "createdAt").is_some());
528    }
529
530    #[tokio::test]
531    async fn test_mock_data_generator_uuid() {
532        let generator = MockDataGenerator {
533            generator_type: GeneratorType::Uuid,
534            config: HashMap::new(),
535        };
536
537        let result = generator.generate().await;
538        assert!(matches!(result, Value::String(_)));
539    }
540
541    #[tokio::test]
542    async fn test_mock_data_generator_email() {
543        let generator = MockDataGenerator {
544            generator_type: GeneratorType::Email,
545            config: HashMap::new(),
546        };
547
548        let result = generator.generate().await;
549        assert!(matches!(result, Value::String(_)));
550    }
551
552    #[tokio::test]
553    async fn test_mock_data_generator_name() {
554        let generator = MockDataGenerator {
555            generator_type: GeneratorType::Name,
556            config: HashMap::new(),
557        };
558
559        let result = generator.generate().await;
560        assert!(matches!(result, Value::String(_)));
561    }
562
563    #[tokio::test]
564    async fn test_mock_data_generator_timestamp() {
565        let generator = MockDataGenerator {
566            generator_type: GeneratorType::Timestamp,
567            config: HashMap::new(),
568        };
569
570        let result = generator.generate().await;
571        assert!(matches!(result, Value::String(_)));
572    }
573
574    #[tokio::test]
575    async fn test_mock_data_generator_int() {
576        let generator = MockDataGenerator {
577            generator_type: GeneratorType::Int { min: 1, max: 100 },
578            config: HashMap::new(),
579        };
580
581        let result = generator.generate().await;
582        assert!(matches!(result, Value::Number(_)));
583    }
584
585    #[tokio::test]
586    async fn test_mock_data_generator_float() {
587        let generator = MockDataGenerator {
588            generator_type: GeneratorType::Float {
589                min: 0.0,
590                max: 10.0,
591            },
592            config: HashMap::new(),
593        };
594
595        let result = generator.generate().await;
596        assert!(matches!(result, Value::Number(_)));
597    }
598
599    #[tokio::test]
600    async fn test_mock_data_generator_string() {
601        let generator = MockDataGenerator {
602            generator_type: GeneratorType::String {
603                min_length: 5,
604                max_length: 10,
605            },
606            config: HashMap::new(),
607        };
608
609        let result = generator.generate().await;
610        if let Value::String(s) = result {
611            assert!(s.len() >= 5);
612            assert!(s.len() <= 10);
613        } else {
614            panic!("Expected string value");
615        }
616    }
617
618    #[tokio::test]
619    async fn test_mock_data_generator_from_list() {
620        let values = vec![
621            serde_json::json!("value1"),
622            serde_json::json!("value2"),
623            serde_json::json!("value3"),
624        ];
625
626        let generator = MockDataGenerator {
627            generator_type: GeneratorType::FromList { values },
628            config: HashMap::new(),
629        };
630
631        let result = generator.generate().await;
632        assert!(matches!(result, Value::String(_)));
633    }
634
635    #[tokio::test]
636    async fn test_mock_data_generator_from_empty_list() {
637        let generator = MockDataGenerator {
638            generator_type: GeneratorType::FromList { values: vec![] },
639            config: HashMap::new(),
640        };
641
642        let result = generator.generate().await;
643        assert!(matches!(result, Value::Null));
644    }
645
646    #[tokio::test]
647    async fn test_mock_data_generator_array() {
648        let generator = MockDataGenerator {
649            generator_type: GeneratorType::Array {
650                item_generator: Box::new(GeneratorType::Uuid),
651                min_items: 2,
652                max_items: 5,
653            },
654            config: HashMap::new(),
655        };
656
657        let result = generator.generate().await;
658        if let Value::List(items) = result {
659            assert!(items.len() >= 2);
660            assert!(items.len() <= 5);
661        } else {
662            panic!("Expected list value");
663        }
664    }
665
666    #[tokio::test]
667    async fn test_resolver_registry_multiple_types() {
668        let mut registry = ResolverRegistry::new();
669
670        registry.register_resolver(
671            "User",
672            MockResolver {
673                field_name: "id".to_string(),
674                field_type: "ID!".to_string(),
675                mock_data: Value::String("user-id".to_string()),
676                generator: None,
677            },
678        );
679
680        registry.register_resolver(
681            "Post",
682            MockResolver {
683                field_name: "id".to_string(),
684                field_type: "ID!".to_string(),
685                mock_data: Value::String("post-id".to_string()),
686                generator: None,
687            },
688        );
689
690        assert!(registry.get_resolver("User", "id").is_some());
691        assert!(registry.get_resolver("Post", "id").is_some());
692    }
693
694    #[test]
695    fn test_resolver_registry_multiple_fields_same_type() {
696        let mut registry = ResolverRegistry::new();
697
698        registry.register_resolver(
699            "User",
700            MockResolver {
701                field_name: "id".to_string(),
702                field_type: "ID!".to_string(),
703                mock_data: Value::Null,
704                generator: None,
705            },
706        );
707
708        registry.register_resolver(
709            "User",
710            MockResolver {
711                field_name: "name".to_string(),
712                field_type: "String!".to_string(),
713                mock_data: Value::Null,
714                generator: None,
715            },
716        );
717
718        registry.register_resolver(
719            "User",
720            MockResolver {
721                field_name: "email".to_string(),
722                field_type: "String!".to_string(),
723                mock_data: Value::Null,
724                generator: None,
725            },
726        );
727
728        assert_eq!(registry.resolvers.get("User").unwrap().len(), 3);
729    }
730}