Skip to main content

jgd_rs/type_spec/
entity.rs

1use std::collections::{HashMap, HashSet};
2
3use indexmap::IndexMap;
4use rand::{rngs::StdRng, SeedableRng};
5use serde::Deserialize;
6use serde_json::Value;
7use crate::{type_spec::{Count, Field, GetCount, JsonGenerator}, JgdGeneratorError, LocalConfig};
8
9/// Creates a fingerprint for uniqueness checking based on specified fields.
10///
11/// This function extracts values from the specified fields in the JSON object
12/// and creates a string fingerprint that can be used to check for duplicates.
13///
14/// # Arguments
15///
16/// * `obj` - The JSON object to create a fingerprint for
17/// * `unique_fields` - Vector of field names to include in the fingerprint
18///
19/// # Returns
20///
21/// A string fingerprint representing the combination of unique field values
22fn fingerprint(obj: &Value, unique_fields: &[String]) -> String {
23    let mut parts = Vec::new();
24
25    if let Value::Object(map) = obj {
26        for field in unique_fields {
27            if let Some(value) = map.get(field) {
28                // Convert the value to a string representation for fingerprinting
29                let value_str = match value {
30                    Value::String(s) => s.clone(),
31                    Value::Number(n) => n.to_string(),
32                    Value::Bool(b) => b.to_string(),
33                    Value::Null => "null".to_string(),
34                    _ => serde_json::to_string(value).unwrap_or_else(|_| "unknown".to_string()),
35                };
36                parts.push(format!("{}:{}", field, value_str));
37            } else {
38                parts.push(format!("{}:missing", field));
39            }
40        }
41    }
42
43    parts.join("|")
44}
45
46/// Represents an entity specification for generating structured data objects in JGD schemas.
47///
48/// `Entity` defines a collection of named fields that together form a structured data object.
49/// It supports generating single objects or arrays of objects, with optional uniqueness
50/// constraints to ensure no duplicate entities based on specified field combinations.
51///
52/// # JGD Schema Integration
53///
54/// Entities are the primary building blocks for creating structured data in JGD schemas.
55/// They correspond to objects in JSON and can contain multiple fields of different types,
56/// creating realistic data structures for testing and development.
57///
58/// # Features
59///
60/// - **Multiple Fields**: Define complex objects with various field types
61/// - **Count Control**: Generate single objects or arrays of objects
62/// - **Uniqueness Constraints**: Ensure entities are unique based on specified fields
63/// - **Seeding Support**: Optional seed for deterministic generation
64/// - **Cross-References**: Fields can reference other generated entities
65///
66/// # JSON Schema Representation
67///
68/// ```json
69/// {
70///   "entity": {
71///     "count": 10,
72///     "unique_by": ["id", "email"],
73///     "fields": {
74///       "id": "${uuid}",
75///       "name": "${name.fullName}",
76///       "email": "${internet.email}",
77///       "age": {
78///         "number": {
79///           "min": 18,
80///           "max": 65,
81///           "integer": true
82///         }
83///       }
84///     }
85///   }
86/// }
87/// ```
88///
89/// # Uniqueness Constraints
90///
91/// The `unique_by` field allows specifying which field combinations must be unique
92/// across all generated entities. This is useful for:
93/// - Primary key constraints (IDs, usernames, emails)
94/// - Composite uniqueness (user + project combinations)
95/// - Realistic data constraints (no duplicate SSNs, etc.)
96///
97/// # Examples
98///
99/// ```rust,ignore
100/// use jgd_rs::{Entity, Field, Count, JsonGenerator, GeneratorConfig};
101/// use indexmap::IndexMap;
102///
103/// let mut config = GeneratorConfig::new("EN", Some(42));
104///
105/// // Create user entities with unique emails
106/// let mut fields = IndexMap::new();
107/// fields.insert("id".to_string(), Field::Str("${uuid}".to_string()));
108/// fields.insert("name".to_string(), Field::Str("${name.fullName}".to_string()));
109/// fields.insert("email".to_string(), Field::Str("${internet.email}".to_string()));
110///
111/// let entity = Entity {
112///     count: Some(Count::Fixed(5)),
113///     seed: None,
114///     unique_by: vec!["email".to_string()],
115///     fields,
116/// };
117///
118/// let result = entity.generate(&mut config);
119/// // Generates an array of 5 user objects with unique emails
120/// ```
121#[derive(Debug, Deserialize, Clone)]
122pub struct Entity {
123    /// Optional count specification for the number of entities to generate.
124    ///
125    /// Determines whether to generate a single entity object or an array of entities:
126    /// - **Some(count)**: Generates an array with the specified count
127    /// - **None**: Generates a single entity object (not wrapped in an array)
128    ///
129    /// This field works with the `GetCount` trait to provide flexible count specifications
130    /// including fixed counts and ranges.
131    ///
132    /// # JSON Schema Mapping
133    ///
134    /// ```json
135    /// {
136    ///   "entity": {
137    ///     "count": 5,        // Generate 5 entities
138    ///     "fields": { ... }
139    ///   }
140    /// }
141    /// ```
142    ///
143    /// Or omitted for single entity:
144    /// ```json
145    /// {
146    ///   "entity": {
147    ///     "fields": { ... }  // Generate single entity
148    ///   }
149    /// }
150    /// ```
151    pub count: Option<Count>,
152
153    /// Optional seed for deterministic entity generation.
154    ///
155    /// When specified, this seed can be used to ensure reproducible entity generation
156    /// for testing and debugging purposes. Currently preserved for future implementation
157    /// of per-entity seeding.
158    ///
159    /// # Future Enhancement
160    ///
161    /// This field is planned for use in scenarios where specific entities need
162    /// their own deterministic generation context, separate from the global
163    /// generator configuration seed.
164    ///
165    /// # JSON Schema Mapping
166    ///
167    /// ```json
168    /// {
169    ///   "entity": {
170    ///     "seed": 12345,
171    ///     "fields": { ... }
172    ///   }
173    /// }
174    /// ```
175    #[serde(default)]
176    pub seed: Option<u64>,
177
178    /// Fields that must be unique across all generated entities.
179    ///
180    /// This vector specifies which field combinations must be unique when generating
181    /// multiple entities. The uniqueness check creates a fingerprint from the specified
182    /// fields and ensures no duplicates are generated.
183    ///
184    /// # Uniqueness Behavior
185    ///
186    /// - **Empty vector**: No uniqueness constraints (default)
187    /// - **Single field**: Ensures that field is unique across entities
188    /// - **Multiple fields**: Ensures the combination of fields is unique
189    ///
190    /// # Performance Considerations
191    ///
192    /// Uniqueness checking has a maximum retry limit (1000 attempts) to prevent
193    /// infinite loops when constraints are too restrictive relative to the possible
194    /// value space.
195    ///
196    /// # JSON Schema Mapping
197    ///
198    /// ```json
199    /// {
200    ///   "entity": {
201    ///     "unique_by": ["id"],           // Single field uniqueness
202    ///     "fields": { ... }
203    ///   }
204    /// }
205    /// ```
206    ///
207    /// Or multiple fields:
208    /// ```json
209    /// {
210    ///   "entity": {
211    ///     "unique_by": ["user_id", "project_id"], // Composite uniqueness
212    ///     "fields": { ... }
213    ///   }
214    /// }
215    /// ```
216    ///
217    /// # Examples
218    ///
219    /// ```rust,ignore
220    /// // Ensure email uniqueness
221    /// unique_by: vec!["email".to_string()]
222    ///
223    /// // Ensure user+project combination uniqueness
224    /// unique_by: vec!["user_id".to_string(), "project_id".to_string()]
225    /// ```
226    #[serde(default)]
227    pub unique_by: Vec<String>,
228
229    /// The collection of fields that make up the entity structure.
230    ///
231    /// This `IndexMap` defines the schema for the generated entities, mapping field
232    /// names to their generation specifications. The use of `IndexMap` preserves
233    /// the field ordering defined in the schema.
234    ///
235    /// # Field Types
236    ///
237    /// Each field can be any valid `Field` type:
238    /// - **Primitive fields**: Strings, numbers, booleans
239    /// - **Complex fields**: Nested entities, arrays
240    /// - **Template fields**: String templates with placeholder substitution
241    /// - **Reference fields**: Cross-references to other generated data
242    /// - **Optional fields**: Fields that may or may not be present
243    ///
244    /// # Field Independence
245    ///
246    /// Each field is generated independently, allowing for:
247    /// - Different fake data for each field
248    /// - Independent randomization
249    /// - Cross-field references when needed
250    ///
251    /// # JSON Schema Mapping
252    ///
253    /// ```json
254    /// {
255    ///   "entity": {
256    ///     "fields": {
257    ///       "id": "${uuid}",
258    ///       "name": "${name.fullName}",
259    ///       "age": {
260    ///         "number": {
261    ///           "min": 18,
262    ///           "max": 65,
263    ///           "integer": true
264    ///         }
265    ///       },
266    ///       "address": {
267    ///         "entity": {
268    ///           "fields": {
269    ///             "street": "${address.streetAddress}",
270    ///             "city": "${address.cityName}"
271    ///           }
272    ///         }
273    ///       }
274    ///     }
275    ///   }
276    /// }
277    /// ```
278    pub fields: IndexMap<String, Field>,
279}
280
281impl JsonGenerator for Entity {
282    /// Generates entities according to the entity specification with uniqueness constraints.
283    ///
284    /// This method creates either a single entity object or an array of entities,
285    /// depending on the count specification. When uniqueness constraints are specified,
286    /// it ensures that no duplicate entities are generated based on the unique field
287    /// combinations.
288    ///
289    /// # Generation Process
290    ///
291    /// 1. **Count Determination**: Uses count specification to determine output format
292    /// 2. **Uniqueness Setup**: Initializes tracking for unique field combinations
293    /// 3. **Entity Generation**: Creates entities with uniqueness validation
294    /// 4. **Output Formation**: Returns single object or array based on count
295    ///
296    /// # Arguments
297    ///
298    /// * `config` - Mutable reference to the generator configuration containing
299    ///   RNG, fake data generators, and cross-reference state
300    ///
301    /// # Returns
302    ///
303    /// - **Single Entity**: `Value::Object` when count is None
304    /// - **Entity Array**: `Value::Array` when count is specified
305    ///
306    /// # Uniqueness Algorithm
307    ///
308    /// When `unique_by` fields are specified:
309    /// 1. Generate a candidate entity
310    /// 2. Create a fingerprint from the unique fields
311    /// 3. Check if fingerprint already exists
312    /// 4. If unique, add to results; if duplicate, retry
313    /// 5. Maximum 1000 attempts per entity to prevent infinite loops
314    ///
315    /// # Error Handling
316    ///
317    /// - **Uniqueness Failures**: Logs warning and stops generation after max attempts
318    /// - **Missing Fields**: Includes "missing" in fingerprint for absent unique fields
319    /// - **Complex Values**: Serializes complex field values for fingerprinting
320    ///
321    /// # Examples
322    ///
323    /// ```rust,ignore
324    /// use jgd_rs::{Entity, Field, Count, JsonGenerator, GeneratorConfig};
325    /// use indexmap::IndexMap;
326    ///
327    /// let mut config = GeneratorConfig::new("EN", Some(42));
328    ///
329    /// // Single entity generation
330    /// let mut fields = IndexMap::new();
331    /// fields.insert("name".to_string(), Field::Str("${name.fullName}".to_string()));
332    /// fields.insert("age".to_string(), Field::I64(25));
333    ///
334    /// let entity = Entity {
335    ///     count: None,  // Single entity
336    ///     seed: None,
337    ///     unique_by: vec![],
338    ///     fields,
339    /// };
340    ///
341    /// let result = entity.generate(&mut config);
342    /// // Returns: {"name": "John Doe", "age": 25}
343    ///
344    /// // Array of unique entities
345    /// let entity_array = Entity {
346    ///     count: Some(Count::Fixed(3)),
347    ///     unique_by: vec!["name".to_string()],
348    ///     fields: fields.clone(),
349    /// };
350    ///
351    /// let result = entity_array.generate(&mut config);
352    /// // Returns: [{"name": "John", "age": 25}, {"name": "Jane", "age": 25}, ...]
353    /// ```
354    ///
355    /// # Performance Notes
356    ///
357    /// - **Memory Efficiency**: Pre-allocates result vector with known capacity
358    /// - **Uniqueness Overhead**: Fingerprinting and set operations add computational cost
359    /// - **Retry Limits**: Maximum attempts prevent infinite loops in constrained scenarios
360    /// - **Fingerprint Storage**: Memory usage grows with unique entity count
361    ///
362    /// # Uniqueness Constraints
363    ///
364    /// Consider the value space when setting uniqueness constraints:
365    /// - **Large Value Space**: Easy to generate unique entities
366    /// - **Small Value Space**: May hit retry limits with restrictive constraints
367    /// - **Template Variety**: Ensure fake data templates provide sufficient variation
368    fn generate(&self, config: &mut super::GeneratorConfig, local_config: Option<&mut LocalConfig>
369        ) -> Result<Value, JgdGeneratorError> {
370        let count_items = self.count.count(config);
371
372        let mut items = Vec::with_capacity(count_items as usize);
373        let mut unique_sets: HashMap<String, HashSet<String>> = HashMap::new();
374
375        let rng = self.seed.map(StdRng::seed_from_u64);
376
377        let mut local_config =
378            LocalConfig::from_current_with_config(rng, Some(count_items), local_config);
379
380        let mut _attempts = 0;
381        const MAX_ATTEMPTS: usize = 1000; // Prevent infinite loops
382
383        for i in 0..count_items {
384            let mut obj = None;
385            local_config.set_index(i as usize);
386
387            // Try to generate a unique object
388            for _ in 0..MAX_ATTEMPTS {
389                _attempts += 1;
390                let candidate = self.fields.generate(config, Some(&mut local_config))?;
391
392                if !self.unique_by.is_empty() {
393                    let fp = fingerprint(&candidate, &self.unique_by);
394                    let set = unique_sets.entry(self.unique_by.join("|"))
395                        .or_default();
396
397                    if !set.contains(&fp) {
398                        set.insert(fp);
399                        obj = Some(candidate);
400                        break;
401                    }
402                    // If fingerprint already exists, try again
403                } else {
404                    // No uniqueness constraints
405                    obj = Some(candidate);
406                    break;
407                }
408            }
409
410            if let Some(generated_obj) = obj {
411                if self.count.is_none() {
412                    return Ok(generated_obj);
413                }
414                items.push(generated_obj);
415            } else {
416                // Failed to generate a unique object after MAX_ATTEMPTS
417                // This can happen if the uniqueness constraints are too restrictive
418                // relative to the possible value space
419                eprintln!("Warning: Failed to generate unique entity after {} attempts. Uniqueness constraints may be too restrictive.", MAX_ATTEMPTS);
420                break;
421            }
422        }
423
424        Ok(Value::Array(items))
425    }
426}
427
428impl JsonGenerator for IndexMap<String, Entity> {
429    /// Generates a collection of named entities and manages cross-references.
430    ///
431    /// This implementation generates multiple entities defined in an `IndexMap`,
432    /// where each entity has a name and specification. It handles both the generation
433    /// of individual entities and the management of cross-references between them.
434    ///
435    /// # Cross-Reference Management
436    ///
437    /// Each generated entity is stored in the global `gen_value` map using its name
438    /// as the key. This enables:
439    /// - References between entities using path notation
440    /// - Consistent data relationships across the generated dataset
441    /// - Template substitution with previously generated values
442    ///
443    /// # Generation Process
444    ///
445    /// 1. **Entity Generation**: Generate each named entity independently
446    /// 2. **Cross-Reference Storage**: Store each entity in the global reference map
447    /// 3. **Object Construction**: Build the final JSON object with all entities
448    ///
449    /// # Arguments
450    ///
451    /// * `config` - Mutable reference to the generator configuration containing
452    ///   the global state for cross-references and generation context
453    ///
454    /// # Returns
455    ///
456    /// A `Value::Object` containing all generated entities, where keys are entity
457    /// names and values are the generated entity data (objects or arrays).
458    ///
459    /// # Cross-Reference Usage
460    ///
461    /// Once entities are generated and stored, they can be referenced in templates:
462    /// - `"author": "${users.name}"` - Reference user entity's name
463    /// - `"post_author": "${posts.author}"` - Reference post entity's author
464    ///
465    /// # JGD Schema Example
466    /// ```json
467    /// {
468    ///   "root": {
469    ///     "users": {
470    ///       "count": { "fixed": 2 },
471    ///       "fields": {
472    ///         "id": { "number": { "from": 1, "to": 1000, "type": "integer" } },
473    ///         "name": { "fake": "name.firstName" }
474    ///       }
475    ///     },
476    ///     "posts": {
477    ///       "count": { "fixed": 3 },
478    ///       "fields": {
479    ///         "user_id": "${users.id}",
480    ///         "title": { "fake": "lorem.sentence(3)" }
481    ///       }
482    ///     }
483    ///   }
484    /// }
485    /// ```
486    ///
487    /// # Examples
488    ///
489    /// ```rust,ignore
490    /// use jgd_rs::{Entity, Field, JsonGenerator, GeneratorConfig};
491    /// use indexmap::IndexMap;
492    ///
493    /// let mut config = GeneratorConfig::new("EN", Some(42));
494    /// let mut entities = IndexMap::new();
495    ///
496    /// // Define user entity
497    /// let mut user_fields = IndexMap::new();
498    /// user_fields.insert("id".to_string(), Field::Str("${uuid}".to_string()));
499    /// user_fields.insert("name".to_string(), Field::Str("${name.fullName}".to_string()));
500    ///
501    /// entities.insert("users".to_string(), Entity {
502    ///     count: Some(Count::Fixed(2)),
503    ///     seed: None,
504    ///     unique_by: vec!["id".to_string()],
505    ///     fields: user_fields,
506    /// });
507    ///
508    /// // Define post entity that references users
509    /// let mut post_fields = IndexMap::new();
510    /// post_fields.insert("title".to_string(), Field::Str("${lorem.sentence}".to_string()));
511    /// post_fields.insert("author_id".to_string(), Field::Ref {
512    ///     r#ref: "users.id".to_string()
513    /// });
514    ///
515    /// entities.insert("posts".to_string(), Entity {
516    ///     count: Some(Count::Fixed(3)),
517    ///     seed: None,
518    ///     unique_by: vec![],
519    ///     fields: post_fields,
520    /// });
521    ///
522    /// let result = entities.generate(&mut config);
523    /// // Returns: {
524    /// //   "users": [{"id": "...", "name": "..."}, ...],
525    /// //   "posts": [{"title": "...", "author_id": "..."}, ...]
526    /// // }
527    /// ```
528    ///
529    /// # Performance Notes
530    ///
531    /// - **Sequential Generation**: Entities are generated in insertion order
532    /// - **Reference Storage**: Each entity is cloned for storage in gen_value
533    /// - **Memory Usage**: Stores both final result and reference copies
534    /// - **Order Dependency**: Earlier entities can be referenced by later ones
535    fn generate(&self, config: &mut super::GeneratorConfig, local_config: Option<&mut LocalConfig>
536        ) -> Result<Value, JgdGeneratorError> {
537        let mut local_config =
538            LocalConfig::from_current_with_config(None, None, local_config);
539
540        let mut map = serde_json::Map::new();
541        for (name, entity) in self {
542            local_config.entity_name = Some(name.clone());
543            let generated = entity.generate(config, Some(&mut local_config))?;
544            map.insert(name.clone(), generated.clone());
545
546            config.gen_value.insert(name.clone(), generated);
547        }
548
549        Ok(Value::Object(map))
550    }
551}
552
553#[cfg(test)]
554mod tests {
555    use super::*;
556    use crate::type_spec::{GeneratorConfig, NumberSpec};
557
558    fn create_test_config(seed: Option<u64>) -> GeneratorConfig {
559        GeneratorConfig::new("EN", seed)
560    }
561
562    #[test]
563    fn test_fingerprint_single_field() {
564        let obj = serde_json::json!({
565            "id": "12345",
566            "name": "John Doe"
567        });
568
569        let fp = fingerprint(&obj, &["id".to_string()]);
570        assert_eq!(fp, "id:12345");
571    }
572
573    #[test]
574    fn test_fingerprint_multiple_fields() {
575        let obj = serde_json::json!({
576            "id": "12345",
577            "name": "John Doe",
578            "email": "john@example.com"
579        });
580
581        let fp = fingerprint(&obj, &["id".to_string(), "email".to_string()]);
582        assert_eq!(fp, "id:12345|email:john@example.com");
583    }
584
585    #[test]
586    fn test_fingerprint_missing_field() {
587        let obj = serde_json::json!({
588            "id": "12345",
589            "name": "John Doe"
590        });
591
592        let fp = fingerprint(&obj, &["id".to_string(), "missing".to_string()]);
593        assert_eq!(fp, "id:12345|missing:missing");
594    }
595
596    #[test]
597    fn test_fingerprint_different_types() {
598        let obj = serde_json::json!({
599            "id": 12345,
600            "active": true,
601            "score": 98.5,
602            "data": null,
603            "tags": ["rust", "json"]
604        });
605
606        let fp = fingerprint(&obj, &[
607            "id".to_string(),
608            "active".to_string(),
609            "score".to_string(),
610            "data".to_string(),
611            "tags".to_string()
612        ]);
613
614        assert!(fp.contains("id:12345"));
615        assert!(fp.contains("active:true"));
616        assert!(fp.contains("score:98.5"));
617        assert!(fp.contains("data:null"));
618        assert!(fp.contains("tags:"));
619    }
620
621    #[test]
622    fn test_entity_single_generation() {
623        let mut config = create_test_config(Some(42));
624        let mut fields = IndexMap::new();
625        fields.insert("name".to_string(), Field::Str("John".to_string()));
626        fields.insert("age".to_string(), Field::I64(30));
627
628        let entity = Entity {
629            count: None,
630            seed: None,
631            unique_by: vec![],
632            fields,
633        };
634
635        let result = entity.generate(&mut config, None);
636        assert!(result.is_ok());
637
638        if let Ok(result) = result {
639            match result {
640                Value::Object(obj) => {
641                    assert_eq!(obj.get("name"), Some(&Value::String("John".to_string())));
642                    assert_eq!(obj.get("age"), Some(&Value::Number(serde_json::Number::from(30))));
643                }
644                _ => panic!("Expected object for single entity"),
645            }
646        }
647    }
648
649    #[test]
650    fn test_entity_array_generation() {
651        let mut config = create_test_config(Some(42));
652        let mut fields = IndexMap::new();
653        fields.insert("id".to_string(), Field::Number {
654            number: NumberSpec::new_integer(1.0, 1000.0)
655        });
656
657        let entity = Entity {
658            count: Some(Count::Fixed(3)),
659            seed: None,
660            unique_by: vec![],
661            fields,
662        };
663
664        let result = entity.generate(&mut config, None);
665        assert!(result.is_ok());
666
667        if let Ok(result) = result {
668            match result {
669                Value::Array(arr) => {
670                    assert_eq!(arr.len(), 3);
671                    for item in arr {
672                        assert!(item.is_object());
673                        if let Value::Object(obj) = item {
674                            assert!(obj.contains_key("id"));
675                            assert!(obj.get("id").unwrap().is_number());
676                        }
677                    }
678                }
679                _ => panic!("Expected array for entity with count"),
680            }
681        }
682    }
683
684    #[test]
685    fn test_entity_uniqueness_constraint() {
686        let mut config = create_test_config(Some(42));
687        let mut fields = IndexMap::new();
688        fields.insert("id".to_string(), Field::Number {
689            number: NumberSpec::new_integer(1.0, 3.0) // Small range to force uniqueness testing
690        });
691        fields.insert("name".to_string(), Field::Str("Test".to_string()));
692
693        let entity = Entity {
694            count: Some(Count::Fixed(3)),
695            seed: None,
696            unique_by: vec!["id".to_string()],
697            fields,
698        };
699
700        let result = entity.generate(&mut config, None);
701        assert!(result.is_ok());
702
703        if let Ok(result) = result {
704            match result {
705                Value::Array(arr) => {
706                    // Should generate up to 3 unique entities (might be less due to small range)
707                    assert!(arr.len() <= 3);
708
709                    // Verify uniqueness
710                    let mut seen_ids = std::collections::HashSet::new();
711                    for item in arr {
712                        if let Value::Object(obj) = item {
713                            if let Some(Value::Number(id)) = obj.get("id") {
714                                let id_value = id.as_i64().unwrap();
715                                assert!(!seen_ids.contains(&id_value), "Duplicate ID found: {}", id_value);
716                                seen_ids.insert(id_value);
717                            }
718                        }
719                    }
720                }
721                _ => panic!("Expected array for entity with count"),
722            }
723        }
724    }
725
726    #[test]
727    fn test_entity_composite_uniqueness() {
728        let mut config = create_test_config(Some(42));
729        let mut fields = IndexMap::new();
730        fields.insert("category".to_string(), Field::Number {
731            number: NumberSpec::new_integer(1.0, 2.0)
732        });
733        fields.insert("subcategory".to_string(), Field::Number {
734            number: NumberSpec::new_integer(1.0, 2.0)
735        });
736
737        let entity = Entity {
738            count: Some(Count::Fixed(5)),
739            seed: None,
740            unique_by: vec!["category".to_string(), "subcategory".to_string()],
741            fields,
742        };
743
744        let result = entity.generate(&mut config, None);
745        assert!(result.is_ok());
746
747        if let Ok(result) = result {
748            match result {
749                Value::Array(arr) => {
750                    // With 2x2 combinations, maximum should be 4 unique entities
751                    assert!(arr.len() <= 4);
752
753                    // Verify composite uniqueness
754                    let mut seen_combinations = std::collections::HashSet::new();
755                    for item in arr {
756                        if let Value::Object(obj) = item {
757                            let cat = obj.get("category").unwrap().as_i64().unwrap();
758                            let subcat = obj.get("subcategory").unwrap().as_i64().unwrap();
759                            let combination = (cat, subcat);
760
761                            assert!(!seen_combinations.contains(&combination),
762                                "Duplicate combination found: {:?}", combination);
763                            seen_combinations.insert(combination);
764                        }
765                    }
766                }
767                _ => panic!("Expected array for entity with count"),
768            }
769        }
770    }
771
772    #[test]
773    fn test_entity_map_generation() {
774        let mut config = create_test_config(Some(42));
775        let mut entities = IndexMap::new();
776
777        // First entity
778        let mut user_fields = IndexMap::new();
779        user_fields.insert("id".to_string(), Field::I64(1));
780        user_fields.insert("name".to_string(), Field::Str("User".to_string()));
781
782        entities.insert("users".to_string(), Entity {
783            count: Some(Count::Fixed(1)),
784            seed: None,
785            unique_by: vec![],
786            fields: user_fields,
787        });
788
789        // Second entity
790        let mut post_fields = IndexMap::new();
791        post_fields.insert("title".to_string(), Field::Str("Post".to_string()));
792
793        entities.insert("posts".to_string(), Entity {
794            count: None,
795            seed: None,
796            unique_by: vec![],
797            fields: post_fields,
798        });
799
800        let result = entities.generate(&mut config, None);
801        assert!(result.is_ok());
802
803        if let Ok(result) = result {
804            match result {
805                Value::Object(obj) => {
806                    assert_eq!(obj.len(), 2);
807                    assert!(obj.contains_key("users"));
808                    assert!(obj.contains_key("posts"));
809
810                    // Users should be an array
811                    assert!(obj.get("users").unwrap().is_array());
812
813                    // Posts should be an object (no count specified)
814                    assert!(obj.get("posts").unwrap().is_object());
815                }
816                _ => panic!("Expected object for entity map"),
817            }
818        }
819    }
820
821    #[test]
822    fn test_entity_cross_reference_storage() {
823        let mut config = create_test_config(Some(42));
824        let mut entities = IndexMap::new();
825
826        let mut user_fields = IndexMap::new();
827        user_fields.insert("name".to_string(), Field::Str("TestUser".to_string()));
828
829        entities.insert("users".to_string(), Entity {
830            count: None,
831            seed: None,
832            unique_by: vec![],
833            fields: user_fields,
834        });
835
836        let _ = entities.generate(&mut config, None);
837
838        // Verify that the entity was stored in gen_value for cross-references
839        assert!(config.gen_value.contains_key("users"));
840
841        let stored_user = config.gen_value.get("users").unwrap();
842        match stored_user {
843            Value::Object(obj) => {
844                assert_eq!(obj.get("name"), Some(&Value::String("TestUser".to_string())));
845            }
846            _ => panic!("Expected stored user to be an object"),
847        }
848    }
849}