Skip to main content

jgd_rs/type_spec/
count.rs

1use rand::Rng;
2use serde::Deserialize;
3
4use crate::type_spec::GeneratorConfig;
5
6/// Represents count specifications for JGD (JSON Generator Definition) entities.
7///
8/// `Count` defines how many items should be generated for arrays, collections,
9/// or repeated elements in JGD schemas. It supports both fixed counts and
10/// dynamic ranges, allowing for flexible data generation scenarios.
11///
12/// # JGD Schema Usage
13///
14/// In JGD schemas, count specifications control the cardinality of generated
15/// data structures. They are commonly used with:
16/// - Array generation (how many array elements to create)
17/// - Entity repetition (how many instances of an entity to generate)
18/// - Collection sizing (dynamic sizing of data collections)
19///
20/// # Variants
21///
22/// - **Fixed(u64)**: Generates exactly the specified number of items
23/// - **Range((u64, u64))**: Generates a random number of items within the range (inclusive)
24///
25/// # Serialization Format
26///
27/// The enum uses `#[serde(untagged)]` for natural JSON representation:
28/// - Fixed count: `42` (just a number)
29/// - Range count: `[5, 10]` (array with min and max values)
30///
31/// # Examples
32///
33/// ```rust,ignore
34/// use jgd_rs::{Count, GetCount, GeneratorConfig};
35///
36/// let mut config = GeneratorConfig::new("EN", Some(42));
37///
38/// // Fixed count - always generates exactly 5 items
39/// let fixed = Count::Fixed(5);
40/// assert_eq!(fixed.count(&mut config), 5);
41///
42/// // Range count - generates between 1 and 10 items
43/// let range = Count::Range((1, 10));
44/// let result = range.count(&mut config);
45/// assert!((1..=10).contains(&result));
46/// ```
47///
48/// # JSON Schema Examples
49///
50/// ```json
51/// {
52///   "array": {
53///     "count": 5,           // Fixed count
54///     "element": { ... }
55///   }
56/// }
57/// ```
58///
59/// ```json
60/// {
61///   "array": {
62///     "count": [1, 10],     // Range count
63///     "element": { ... }
64///   }
65/// }
66/// ```
67#[derive(Debug, Deserialize, Clone)]
68#[serde(untagged)]
69pub enum Count {
70    /// A fixed count that always generates exactly the specified number of items.
71    ///
72    /// This variant is used when you need a consistent, predictable number of
73    /// generated items. The value represents the exact count to generate.
74    ///
75    /// # JSON Representation
76    /// ```json
77    /// 42
78    /// ```
79    ///
80    /// # Use Cases
81    /// - Testing scenarios requiring consistent data sizes
82    /// - Schema definitions with fixed requirements
83    /// - Performance testing with known data volumes
84    Fixed(u64),
85
86    /// A range count that generates a random number of items within the specified bounds.
87    ///
88    /// The tuple contains `(min, max)` values where both bounds are inclusive.
89    /// The actual count is randomly determined each time `count()` is called.
90    ///
91    /// # JSON Representation
92    /// ```json
93    /// [5, 15]
94    /// ```
95    ///
96    /// # Use Cases
97    /// - Realistic data generation with natural variation
98    /// - Stress testing with variable load sizes
99    /// - Simulating real-world data patterns
100    Range((u64,u64))
101}
102
103/// Trait for extracting count values from count specifications.
104///
105/// This trait provides a unified interface for obtaining count values from
106/// different types that can specify counts in JGD generation. It abstracts
107/// over the different ways counts can be represented and ensures consistent
108/// behavior across the generation system.
109///
110/// # Design Philosophy
111///
112/// The trait allows for extensible count specifications while maintaining
113/// a simple interface. It takes a mutable reference to `GeneratorConfig`
114/// to access the random number generator for range-based counts.
115///
116/// # Implementations
117///
118/// - `Count`: Direct count specification (fixed or range)
119/// - `Option<Count>`: Optional count with default fallback
120///
121/// # Examples
122///
123/// ```rust,ignore
124/// use jgd_rs::{Count, GetCount, GeneratorConfig};
125///
126/// let mut config = GeneratorConfig::new("EN", Some(42));
127///
128/// // Using Count directly
129/// let count = Count::Range((1, 5));
130/// let result = count.count(&mut config);
131///
132/// // Using Option<Count> with Some
133/// let opt_count = Some(Count::Fixed(3));
134/// let result = opt_count.count(&mut config);
135///
136/// // Using Option<Count> with None (defaults to 1)
137/// let none_count: Option<Count> = None;
138/// let result = none_count.count(&mut config); // Returns 1
139/// ```
140pub trait GetCount {
141    /// Generates a count value using the provided generator configuration.
142    ///
143    /// This method produces a `u64` count value according to the implementing
144    /// type's specification. For fixed counts, it returns the constant value.
145    /// For range counts, it uses the random number generator to produce a
146    /// value within the specified bounds.
147    ///
148    /// # Arguments
149    ///
150    /// * `config` - Mutable reference to the generator configuration containing
151    ///   the random number generator and other generation context
152    ///
153    /// # Returns
154    ///
155    /// A `u64` representing the number of items to generate
156    ///
157    /// # Examples
158    ///
159    /// ```rust,ignore
160    /// use jgd_rs::{Count, GetCount, GeneratorConfig};
161    ///
162    /// let mut config = GeneratorConfig::new("EN", Some(42));
163    ///
164    /// let fixed = Count::Fixed(5);
165    /// assert_eq!(fixed.count(&mut config), 5);
166    ///
167    /// let range = Count::Range((1, 10));
168    /// let result = range.count(&mut config);
169    /// assert!((1..=10).contains(&result));
170    /// ```
171    fn count(&self, config: &mut GeneratorConfig) -> u64;
172}
173
174impl GetCount for Count {
175    /// Generates a count value based on the Count variant.
176    ///
177    /// This implementation handles both fixed and range count specifications:
178    ///
179    /// - **Fixed**: Returns the constant value immediately
180    /// - **Range**: Uses the RNG to generate a random value within the inclusive range
181    ///
182    /// # Deterministic Behavior
183    ///
184    /// When the `GeneratorConfig` is created with a seed, range-based counts
185    /// will produce deterministic results, which is useful for:
186    /// - Testing with reproducible data
187    /// - Debugging generation issues
188    /// - Creating consistent development datasets
189    ///
190    /// # Range Semantics
191    ///
192    /// Range bounds are inclusive on both ends. A range of `(5, 10)` can
193    /// generate any value from 5 to 10, including both 5 and 10.
194    ///
195    /// # Examples
196    ///
197    /// ```rust,ignore
198    /// use jgd_rs::{Count, GetCount, GeneratorConfig};
199    ///
200    /// let mut config = GeneratorConfig::new("EN", Some(42));
201    ///
202    /// // Fixed count always returns the same value
203    /// let fixed = Count::Fixed(7);
204    /// assert_eq!(fixed.count(&mut config), 7);
205    /// assert_eq!(fixed.count(&mut config), 7); // Always 7
206    ///
207    /// // Range count varies within bounds
208    /// let range = Count::Range((3, 8));
209    /// for _ in 0..10 {
210    ///     let result = range.count(&mut config);
211    ///     assert!((3..=8).contains(&result));
212    /// }
213    /// ```
214    fn count(&self, config: &mut GeneratorConfig) -> u64 {
215        match self {
216            Count::Fixed(n) => *n,
217            Count::Range((a, b)) => config.rng.random_range(*a..=*b),
218        }
219    }
220}
221
222impl GetCount for Option<Count> {
223    /// Generates a count value from an optional Count specification.
224    ///
225    /// This implementation provides convenient default behavior for optional
226    /// count specifications commonly used in JGD schemas where count may be
227    /// omitted.
228    ///
229    /// # Behavior
230    ///
231    /// - **Some(count)**: Delegates to the wrapped `Count`'s implementation
232    /// - **None**: Defaults to `Count::Fixed(1)`, generating exactly 1 item
233    ///
234    /// # Default Semantics
235    ///
236    /// The default count of 1 is chosen because:
237    /// - It's the most common case for single item generation
238    /// - It avoids empty collections which might break downstream processing
239    /// - It provides sensible behavior for optional array sizes
240    ///
241    /// # JGD Schema Integration
242    ///
243    /// This implementation allows JGD schemas to omit count specifications
244    /// when single-item generation is desired, making schemas more concise:
245    ///
246    /// ```json
247    /// {
248    ///   "array": {
249    ///     // count omitted - defaults to 1
250    ///     "element": { "type": "string" }
251    ///   }
252    /// }
253    /// ```
254    ///
255    /// # Examples
256    ///
257    /// ```rust,ignore
258    /// use jgd_rs::{Count, GetCount, GeneratorConfig};
259    ///
260    /// let mut config = GeneratorConfig::new("EN", Some(42));
261    ///
262    /// // Some count uses the wrapped specification
263    /// let some_count = Some(Count::Range((2, 5)));
264    /// let result = some_count.count(&mut config);
265    /// assert!((2..=5).contains(&result));
266    ///
267    /// // None count defaults to 1
268    /// let none_count: Option<Count> = None;
269    /// assert_eq!(none_count.count(&mut config), 1);
270    /// ```
271    fn count(&self, config: &mut GeneratorConfig) -> u64 {
272        self.clone().unwrap_or(Count::Fixed(1)).count(config)
273    }
274}
275
276#[cfg(test)]
277mod tests {
278    use super::*;
279    use crate::type_spec::GeneratorConfig;
280    use rand::rngs::StdRng;
281    use rand::SeedableRng;
282
283    fn create_test_config(seed: Option<u64>) -> GeneratorConfig {
284        GeneratorConfig::new("EN", seed)
285    }
286
287    #[test]
288    fn test_count_fixed() {
289        let mut config = create_test_config(None);
290        let count = Count::Fixed(5);
291
292        assert_eq!(count.count(&mut config), 5);
293        // Should always return the same value
294        assert_eq!(count.count(&mut config), 5);
295        assert_eq!(count.count(&mut config), 5);
296    }
297
298    #[test]
299    fn test_count_fixed_zero() {
300        let mut config = create_test_config(None);
301        let count = Count::Fixed(0);
302
303        assert_eq!(count.count(&mut config), 0);
304    }
305
306    #[test]
307    fn test_count_fixed_large_number() {
308        let mut config = create_test_config(None);
309        let count = Count::Fixed(1000);
310
311        assert_eq!(count.count(&mut config), 1000);
312    }
313
314    #[test]
315    fn test_count_range_single_value() {
316        let mut config = create_test_config(None);
317        let count = Count::Range((5, 5));
318
319        // Range with same min and max should always return that value
320        assert_eq!(count.count(&mut config), 5);
321        assert_eq!(count.count(&mut config), 5);
322    }
323
324    #[test]
325    fn test_count_range_within_bounds() {
326        let mut config = create_test_config(None);
327        let count = Count::Range((1, 10));
328
329        // Test multiple times to ensure values are within range
330        for _ in 0..20 {
331            let result = count.count(&mut config);
332            assert!((1..=10).contains(&result), "Value {} not in range [1, 10]", result);
333        }
334    }
335
336    #[test]
337    fn test_count_range_zero_to_value() {
338        let mut config = create_test_config(None);
339        let count = Count::Range((0, 3));
340
341        for _ in 0..20 {
342            let result = count.count(&mut config);
343            assert!(result <= 3, "Value {} exceeds maximum 3", result);
344        }
345    }
346
347    #[test]
348    fn test_count_range_large_range() {
349        let mut config = create_test_config(None);
350        let count = Count::Range((100, 200));
351
352        for _ in 0..10 {
353            let result = count.count(&mut config);
354            assert!((100..=200).contains(&result), "Value {} not in range [100, 200]", result);
355        }
356    }
357
358    #[test]
359    fn test_count_range_deterministic_with_seed() {
360        // Test that the same seed produces the same sequence
361        let count = Count::Range((1, 100));
362
363        let mut config1 = create_test_config(Some(21));
364        let mut config2 = create_test_config(Some(21));
365
366        // Both configs use the same seed, so should produce same sequence
367        let result1 = count.count(&mut config1);
368        let result2 = count.count(&mut config2);
369
370        assert_eq!(result1, result2, "Same seed should produce same results");
371    }
372
373    #[test]
374    fn test_option_count_some_fixed() {
375        let mut config = create_test_config(None);
376        let opt_count = Some(Count::Fixed(7));
377
378        assert_eq!(opt_count.count(&mut config), 7);
379    }
380
381    #[test]
382    fn test_option_count_some_range() {
383        let mut config = create_test_config(None);
384        let opt_count = Some(Count::Range((2, 8)));
385
386        for _ in 0..10 {
387            let result = opt_count.count(&mut config);
388            assert!((2..=8).contains(&result), "Value {} not in range [2, 8]", result);
389        }
390    }
391
392    #[test]
393    fn test_option_count_none() {
394        let mut config = create_test_config(None);
395        let opt_count: Option<Count> = None;
396
397        // None should default to Count::Fixed(1)
398        assert_eq!(opt_count.count(&mut config), 1);
399        assert_eq!(opt_count.count(&mut config), 1);
400    }
401
402    #[test]
403    fn test_count_clone() {
404        let count = Count::Fixed(42);
405        let cloned = count.clone();
406
407        let mut config = create_test_config(None);
408        assert_eq!(count.count(&mut config), cloned.count(&mut config));
409    }
410
411    #[test]
412    fn test_count_debug() {
413        let fixed = Count::Fixed(10);
414        let range = Count::Range((5, 15));
415
416        // Test that Debug is implemented (should not panic)
417        let _ = format!("{:?}", fixed);
418        let _ = format!("{:?}", range);
419    }
420
421    #[test]
422    fn test_count_deserialize_fixed() {
423        use serde_json;
424
425        let json = "42";
426        let count: Count = serde_json::from_str(json).unwrap();
427
428        match count {
429            Count::Fixed(n) => assert_eq!(n, 42),
430            Count::Range(_) => panic!("Expected Fixed variant"),
431        }
432    }
433
434    #[test]
435    fn test_count_deserialize_range() {
436        use serde_json;
437
438        let json = "[5, 10]";
439        let count: Count = serde_json::from_str(json).unwrap();
440
441        match count {
442            Count::Range((a, b)) => {
443                assert_eq!(a, 5);
444                assert_eq!(b, 10);
445            },
446            Count::Fixed(_) => panic!("Expected Range variant"),
447        }
448    }
449
450    #[test]
451    fn test_count_deserialize_invalid() {
452        use serde_json;
453
454        let invalid_json = "\"not_a_number\"";
455        let result: Result<Count, _> = serde_json::from_str(invalid_json);
456
457        assert!(result.is_err(), "Should fail to deserialize invalid input");
458    }
459
460    #[test]
461    fn test_count_range_distribution() {
462        let mut config = create_test_config(None);
463        let count = Count::Range((1, 3));
464
465        let mut results = std::collections::HashMap::new();
466
467        // Generate many samples to check distribution
468        for _ in 0..300 {
469            let result = count.count(&mut config);
470            *results.entry(result).or_insert(0) += 1;
471        }
472
473        // Should have generated all possible values
474        assert!(results.contains_key(&1), "Should generate value 1");
475        assert!(results.contains_key(&2), "Should generate value 2");
476        assert!(results.contains_key(&3), "Should generate value 3");
477
478        // Should not generate values outside range
479        assert!(!results.contains_key(&0), "Should not generate value 0");
480        assert!(!results.contains_key(&4), "Should not generate value 4");
481    }
482
483    #[test]
484    fn test_multiple_configs_independence() {
485        let count = Count::Range((1, 100));
486
487        // Create configs with different seeds
488        let mut config1 = create_test_config(None);
489        config1.rng = StdRng::seed_from_u64(123);
490
491        let mut config2 = create_test_config(None);
492        config2.rng = StdRng::seed_from_u64(456);
493
494        let result1 = count.count(&mut config1);
495        let result2 = count.count(&mut config2);
496
497        // With different seeds, results might be different (not guaranteed, but likely)
498        // At minimum, both should be in valid range
499        assert!((1..=100).contains(&result1));
500        assert!((1..=100).contains(&result2));
501    }
502
503    #[test]
504    fn test_edge_case_max_u64() {
505        let mut config = create_test_config(None);
506        let large_value = u64::MAX - 1;
507        let count = Count::Fixed(large_value);
508
509        assert_eq!(count.count(&mut config), large_value);
510    }
511}