use std::collections::{HashMap, HashSet};
use indexmap::IndexMap;
use rand::{rngs::StdRng, SeedableRng};
use serde::Deserialize;
use serde_json::Value;
use crate::{type_spec::{Count, Field, GetCount, JsonGenerator}, JgdGeneratorError, LocalConfig};
fn fingerprint(obj: &Value, unique_fields: &[String]) -> String {
let mut parts = Vec::new();
if let Value::Object(map) = obj {
for field in unique_fields {
if let Some(value) = map.get(field) {
let value_str = match value {
Value::String(s) => s.clone(),
Value::Number(n) => n.to_string(),
Value::Bool(b) => b.to_string(),
Value::Null => "null".to_string(),
_ => serde_json::to_string(value).unwrap_or_else(|_| "unknown".to_string()),
};
parts.push(format!("{}:{}", field, value_str));
} else {
parts.push(format!("{}:missing", field));
}
}
}
parts.join("|")
}
#[derive(Debug, Deserialize, Clone)]
pub struct Entity {
pub count: Option<Count>,
#[serde(default)]
pub seed: Option<u64>,
#[serde(default)]
pub unique_by: Vec<String>,
pub fields: IndexMap<String, Field>,
}
impl JsonGenerator for Entity {
fn generate(&self, config: &mut super::GeneratorConfig, local_config: Option<&mut LocalConfig>
) -> Result<Value, JgdGeneratorError> {
let count_items = self.count.count(config);
let mut items = Vec::with_capacity(count_items as usize);
let mut unique_sets: HashMap<String, HashSet<String>> = HashMap::new();
let rng = self.seed.map(StdRng::seed_from_u64);
let mut local_config =
LocalConfig::from_current_with_config(rng, Some(count_items), local_config);
let mut _attempts = 0;
const MAX_ATTEMPTS: usize = 1000;
for i in 0..count_items {
let mut obj = None;
local_config.set_index(i as usize);
for _ in 0..MAX_ATTEMPTS {
_attempts += 1;
let candidate = self.fields.generate(config, Some(&mut local_config))?;
if !self.unique_by.is_empty() {
let fp = fingerprint(&candidate, &self.unique_by);
let set = unique_sets.entry(self.unique_by.join("|"))
.or_default();
if !set.contains(&fp) {
set.insert(fp);
obj = Some(candidate);
break;
}
} else {
obj = Some(candidate);
break;
}
}
if let Some(generated_obj) = obj {
if self.count.is_none() {
return Ok(generated_obj);
}
items.push(generated_obj);
} else {
eprintln!("Warning: Failed to generate unique entity after {} attempts. Uniqueness constraints may be too restrictive.", MAX_ATTEMPTS);
break;
}
}
Ok(Value::Array(items))
}
}
impl JsonGenerator for IndexMap<String, Entity> {
fn generate(&self, config: &mut super::GeneratorConfig, local_config: Option<&mut LocalConfig>
) -> Result<Value, JgdGeneratorError> {
let mut local_config =
LocalConfig::from_current_with_config(None, None, local_config);
let mut map = serde_json::Map::new();
for (name, entity) in self {
local_config.entity_name = Some(name.clone());
let generated = entity.generate(config, Some(&mut local_config))?;
map.insert(name.clone(), generated.clone());
config.gen_value.insert(name.clone(), generated);
}
Ok(Value::Object(map))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::type_spec::{GeneratorConfig, NumberSpec};
fn create_test_config(seed: Option<u64>) -> GeneratorConfig {
GeneratorConfig::new("EN", seed)
}
#[test]
fn test_fingerprint_single_field() {
let obj = serde_json::json!({
"id": "12345",
"name": "John Doe"
});
let fp = fingerprint(&obj, &["id".to_string()]);
assert_eq!(fp, "id:12345");
}
#[test]
fn test_fingerprint_multiple_fields() {
let obj = serde_json::json!({
"id": "12345",
"name": "John Doe",
"email": "john@example.com"
});
let fp = fingerprint(&obj, &["id".to_string(), "email".to_string()]);
assert_eq!(fp, "id:12345|email:john@example.com");
}
#[test]
fn test_fingerprint_missing_field() {
let obj = serde_json::json!({
"id": "12345",
"name": "John Doe"
});
let fp = fingerprint(&obj, &["id".to_string(), "missing".to_string()]);
assert_eq!(fp, "id:12345|missing:missing");
}
#[test]
fn test_fingerprint_different_types() {
let obj = serde_json::json!({
"id": 12345,
"active": true,
"score": 98.5,
"data": null,
"tags": ["rust", "json"]
});
let fp = fingerprint(&obj, &[
"id".to_string(),
"active".to_string(),
"score".to_string(),
"data".to_string(),
"tags".to_string()
]);
assert!(fp.contains("id:12345"));
assert!(fp.contains("active:true"));
assert!(fp.contains("score:98.5"));
assert!(fp.contains("data:null"));
assert!(fp.contains("tags:"));
}
#[test]
fn test_entity_single_generation() {
let mut config = create_test_config(Some(42));
let mut fields = IndexMap::new();
fields.insert("name".to_string(), Field::Str("John".to_string()));
fields.insert("age".to_string(), Field::I64(30));
let entity = Entity {
count: None,
seed: None,
unique_by: vec![],
fields,
};
let result = entity.generate(&mut config, None);
assert!(result.is_ok());
if let Ok(result) = result {
match result {
Value::Object(obj) => {
assert_eq!(obj.get("name"), Some(&Value::String("John".to_string())));
assert_eq!(obj.get("age"), Some(&Value::Number(serde_json::Number::from(30))));
}
_ => panic!("Expected object for single entity"),
}
}
}
#[test]
fn test_entity_array_generation() {
let mut config = create_test_config(Some(42));
let mut fields = IndexMap::new();
fields.insert("id".to_string(), Field::Number {
number: NumberSpec::new_integer(1.0, 1000.0)
});
let entity = Entity {
count: Some(Count::Fixed(3)),
seed: None,
unique_by: vec![],
fields,
};
let result = entity.generate(&mut config, None);
assert!(result.is_ok());
if let Ok(result) = result {
match result {
Value::Array(arr) => {
assert_eq!(arr.len(), 3);
for item in arr {
assert!(item.is_object());
if let Value::Object(obj) = item {
assert!(obj.contains_key("id"));
assert!(obj.get("id").unwrap().is_number());
}
}
}
_ => panic!("Expected array for entity with count"),
}
}
}
#[test]
fn test_entity_uniqueness_constraint() {
let mut config = create_test_config(Some(42));
let mut fields = IndexMap::new();
fields.insert("id".to_string(), Field::Number {
number: NumberSpec::new_integer(1.0, 3.0) });
fields.insert("name".to_string(), Field::Str("Test".to_string()));
let entity = Entity {
count: Some(Count::Fixed(3)),
seed: None,
unique_by: vec!["id".to_string()],
fields,
};
let result = entity.generate(&mut config, None);
assert!(result.is_ok());
if let Ok(result) = result {
match result {
Value::Array(arr) => {
assert!(arr.len() <= 3);
let mut seen_ids = std::collections::HashSet::new();
for item in arr {
if let Value::Object(obj) = item {
if let Some(Value::Number(id)) = obj.get("id") {
let id_value = id.as_i64().unwrap();
assert!(!seen_ids.contains(&id_value), "Duplicate ID found: {}", id_value);
seen_ids.insert(id_value);
}
}
}
}
_ => panic!("Expected array for entity with count"),
}
}
}
#[test]
fn test_entity_composite_uniqueness() {
let mut config = create_test_config(Some(42));
let mut fields = IndexMap::new();
fields.insert("category".to_string(), Field::Number {
number: NumberSpec::new_integer(1.0, 2.0)
});
fields.insert("subcategory".to_string(), Field::Number {
number: NumberSpec::new_integer(1.0, 2.0)
});
let entity = Entity {
count: Some(Count::Fixed(5)),
seed: None,
unique_by: vec!["category".to_string(), "subcategory".to_string()],
fields,
};
let result = entity.generate(&mut config, None);
assert!(result.is_ok());
if let Ok(result) = result {
match result {
Value::Array(arr) => {
assert!(arr.len() <= 4);
let mut seen_combinations = std::collections::HashSet::new();
for item in arr {
if let Value::Object(obj) = item {
let cat = obj.get("category").unwrap().as_i64().unwrap();
let subcat = obj.get("subcategory").unwrap().as_i64().unwrap();
let combination = (cat, subcat);
assert!(!seen_combinations.contains(&combination),
"Duplicate combination found: {:?}", combination);
seen_combinations.insert(combination);
}
}
}
_ => panic!("Expected array for entity with count"),
}
}
}
#[test]
fn test_entity_map_generation() {
let mut config = create_test_config(Some(42));
let mut entities = IndexMap::new();
let mut user_fields = IndexMap::new();
user_fields.insert("id".to_string(), Field::I64(1));
user_fields.insert("name".to_string(), Field::Str("User".to_string()));
entities.insert("users".to_string(), Entity {
count: Some(Count::Fixed(1)),
seed: None,
unique_by: vec![],
fields: user_fields,
});
let mut post_fields = IndexMap::new();
post_fields.insert("title".to_string(), Field::Str("Post".to_string()));
entities.insert("posts".to_string(), Entity {
count: None,
seed: None,
unique_by: vec![],
fields: post_fields,
});
let result = entities.generate(&mut config, None);
assert!(result.is_ok());
if let Ok(result) = result {
match result {
Value::Object(obj) => {
assert_eq!(obj.len(), 2);
assert!(obj.contains_key("users"));
assert!(obj.contains_key("posts"));
assert!(obj.get("users").unwrap().is_array());
assert!(obj.get("posts").unwrap().is_object());
}
_ => panic!("Expected object for entity map"),
}
}
}
#[test]
fn test_entity_cross_reference_storage() {
let mut config = create_test_config(Some(42));
let mut entities = IndexMap::new();
let mut user_fields = IndexMap::new();
user_fields.insert("name".to_string(), Field::Str("TestUser".to_string()));
entities.insert("users".to_string(), Entity {
count: None,
seed: None,
unique_by: vec![],
fields: user_fields,
});
let _ = entities.generate(&mut config, None);
assert!(config.gen_value.contains_key("users"));
let stored_user = config.gen_value.get("users").unwrap();
match stored_user {
Value::Object(obj) => {
assert_eq!(obj.get("name"), Some(&Value::String("TestUser".to_string())));
}
_ => panic!("Expected stored user to be an object"),
}
}
}