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}