use std::{fs, path::PathBuf, sync::{LazyLock, Mutex}};
use indexmap::IndexMap;
use serde::Deserialize;
use serde_json::Value;
use crate::{type_spec::{Entity, GeneratorConfig, JsonGenerator}, CustomKeyFunction, JgdGeneratorError, JgdGlobalConfig};
fn default_locale() -> String {
"EN".to_string()
}
#[derive(Debug, Deserialize)]
pub struct Jgd {
#[serde(rename = "$format")]
#[allow(dead_code)]
pub format: String,
#[allow(dead_code)]
pub version: String,
#[serde(default)]
pub seed: Option<u64>,
#[serde(default = "default_locale", rename = "defaultLocale")]
pub default_locale: String,
#[serde(default)]
pub entities: Option<IndexMap<String, Entity>>,
#[serde(default)]
pub root: Option<Entity>,
}
static GLOBAL_CONFIG: LazyLock<Mutex<JgdGlobalConfig>> = LazyLock::new(|| Mutex::new(JgdGlobalConfig::new()));
impl Jgd {
pub fn from_file(path: &PathBuf) -> Self {
let jgd_string = fs::read_to_string(path);
Self::from(jgd_string.unwrap())
}
pub fn create_config(&self) -> GeneratorConfig {
GeneratorConfig::new(&self.default_locale, self.seed)
}
pub fn generate(&self) -> Result<Value, JgdGeneratorError> {
let mut config = self.create_config();
if let Some(root) = &self.root {
return root.generate(&mut config, None);
}
if let Some(entities) = &self.entities {
return entities.generate(&mut config, None);
}
Ok(Value::Null)
}
pub fn add_custom_key(key: &'static str, func: CustomKeyFunction) {
if let Ok(mut config) = GLOBAL_CONFIG.lock() {
config.custom_keys.insert(key, func);
}
}
pub fn get_custom_key(key: &str) -> Option<CustomKeyFunction> {
if let Ok(config) = GLOBAL_CONFIG.lock() {
if let Some(func) = config.custom_keys.get(key) {
return Some(func.clone());
}
}
None
}
}
impl From<&str> for Jgd {
fn from(value: &str) -> Self {
serde_json::from_str(value).unwrap()
}
}
impl From<String> for Jgd {
fn from(value: String) -> Self {
serde_json::from_str(&value).unwrap()
}
}
impl From<Value> for Jgd {
fn from(value: Value) -> Self {
serde_json::from_value(value).unwrap()
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use crate::Arguments;
use super::*;
use serde_json::json;
#[test]
fn test_default_locale_function() {
assert_eq!(default_locale(), "EN");
}
#[test]
fn test_jgd_from_str_root_mode() {
let schema_json = r#"{
"$format": "jgd/v1",
"version": "1.0.0",
"seed": 42,
"defaultLocale": "EN",
"root": {
"fields": {
"name": "John Doe",
"age": 30
}
}
}"#;
let jgd = Jgd::from(schema_json);
assert_eq!(jgd.format, "jgd/v1");
assert_eq!(jgd.version, "1.0.0");
assert_eq!(jgd.seed, Some(42));
assert_eq!(jgd.default_locale, "EN");
assert!(jgd.root.is_some());
assert!(jgd.entities.is_none());
}
#[test]
fn test_jgd_from_str_entities_mode() {
let schema_json = r#"{
"$format": "jgd/v1",
"version": "2.0.0",
"entities": {
"users": {
"fields": {
"name": "Alice"
}
},
"posts": {
"fields": {
"title": "Test Post"
}
}
}
}"#;
let jgd = Jgd::from(schema_json);
assert_eq!(jgd.format, "jgd/v1");
assert_eq!(jgd.version, "2.0.0");
assert_eq!(jgd.seed, None);
assert_eq!(jgd.default_locale, "EN"); assert!(jgd.root.is_none());
assert!(jgd.entities.is_some());
let entities = jgd.entities.unwrap();
assert!(entities.contains_key("users"));
assert!(entities.contains_key("posts"));
}
#[test]
fn test_jgd_from_string() {
let schema_json = String::from(r#"{
"$format": "jgd/v1",
"version": "1.0.0",
"root": {
"fields": {
"message": "Hello"
}
}
}"#);
let jgd = Jgd::from(schema_json);
assert_eq!(jgd.format, "jgd/v1");
assert!(jgd.root.is_some());
}
#[test]
fn test_jgd_from_value() {
let schema_value = json!({
"$format": "jgd/v1",
"version": "1.0.0",
"seed": 123,
"defaultLocale": "FR",
"root": {
"fields": {
"greeting": "Bonjour"
}
}
});
let jgd = Jgd::from(schema_value);
assert_eq!(jgd.format, "jgd/v1");
assert_eq!(jgd.seed, Some(123));
assert_eq!(jgd.default_locale, "FR");
assert!(jgd.root.is_some());
}
#[test]
fn test_jgd_default_values() {
let schema_json = r#"{
"$format": "jgd/v1",
"version": "1.0",
"root": {
"fields": {
"test": "value"
}
}
}"#;
let jgd = Jgd::from(schema_json);
assert_eq!(jgd.seed, None);
assert_eq!(jgd.default_locale, "EN");
assert!(jgd.entities.is_none());
}
#[test]
fn test_create_config() {
let jgd = Jgd::from(r#"{
"$format": "jgd/v1",
"version": "1.0.0",
"seed": 42,
"defaultLocale": "ES",
"root": {
"fields": {
"test": "value"
}
}
}"#);
let _config = jgd.create_config();
}
#[test]
fn test_generate_root_mode() {
let jgd = Jgd::from(r#"{
"$format": "jgd/v1",
"version": "1.0.0",
"seed": 42,
"root": {
"fields": {
"name": "John Doe",
"age": 30,
"active": true
}
}
}"#);
let result = jgd.generate();
assert!(result.is_ok());
if let Ok(result) = result {
assert!(result.is_object());
if let Value::Object(obj) = result {
assert_eq!(obj.get("name"), Some(&Value::String("John Doe".to_string())));
assert_eq!(obj.get("age"), Some(&Value::Number(serde_json::Number::from(30))));
assert_eq!(obj.get("active"), Some(&Value::Bool(true)));
}
}
}
#[test]
fn test_generate_entities_mode() {
let jgd = Jgd::from(r#"{
"$format": "jgd/v1",
"version": "1.0.0",
"seed": 42,
"entities": {
"users": {
"fields": {
"name": "Alice"
}
},
"products": {
"fields": {
"title": "Product A"
}
}
}
}"#);
let result = jgd.generate();
assert!(result.is_ok());
if let Ok(result) = result {
assert!(result.is_object());
if let Value::Object(obj) = result {
assert!(obj.contains_key("users"));
assert!(obj.contains_key("products"));
if let Some(Value::Object(users)) = obj.get("users") {
assert_eq!(users.get("name"), Some(&Value::String("Alice".to_string())));
}
if let Some(Value::Object(products)) = obj.get("products") {
assert_eq!(products.get("title"), Some(&Value::String("Product A".to_string())));
}
}
}
}
#[test]
fn test_generate_empty_schema() {
let jgd = Jgd::from(r#"{
"$format": "jgd/v1",
"version": "1.0"
}"#);
let result = jgd.generate();
assert!(result.is_ok());
if let Ok(result) = result {
assert_eq!(result, Value::Null);
}
}
#[test]
fn test_generate_deterministic_with_seed() {
let schema = r#"{
"$format": "jgd/v1",
"version": "1.0.0",
"seed": 42,
"root": {
"fields": {
"random_number": {
"number": {
"min": 1,
"max": 100,
"integer": true
}
}
}
}
}"#;
let jgd1 = Jgd::from(schema);
let jgd2 = Jgd::from(schema);
let result1 = jgd1.generate();
let result2 = jgd2.generate();
assert!(result1.is_ok());
assert!(result2.is_ok());
if let Ok(result1) = result1 {
if let Ok(result2) = result2 {
assert_eq!(result1, result2);
}
}
}
#[test]
fn test_generate_different_seeds_different_results() {
let schema_template = |seed: u64| format!(r#"{{
"$format": "jgd/v1",
"version": "1.0.0",
"seed": {},
"root": {{
"fields": {{
"random_number": {{
"number": {{
"min": 1,
"max": 1000,
"integer": true
}}
}}
}}
}}
}}"#, seed);
let jgd1 = Jgd::from(schema_template(42).as_str());
let jgd2 = Jgd::from(schema_template(24).as_str());
let result1 = jgd1.generate();
let result2 = jgd2.generate();
assert!(result1.is_ok());
assert!(result2.is_ok());
if let Ok(result1) = result1 {
if let Ok(result2) = result2 {
assert_ne!(result1, result2);
}
}
}
#[test]
fn test_jgd_debug() {
let jgd = Jgd::from(r#"{
"$format": "jgd/v1",
"version": "test",
"root": {
"fields": {
"test": "value"
}
}
}"#);
let debug_str = format!("{:?}", jgd);
assert!(debug_str.contains("Jgd"));
assert!(debug_str.contains("jgd/v1"));
assert!(debug_str.contains("test"));
}
#[test]
fn test_complex_root_mode_with_count() {
let jgd = Jgd::from(r#"{
"$format": "jgd/v1",
"version": "1.0.0",
"seed": 42,
"root": {
"count": 2,
"fields": {
"id": {
"number": {
"min": 1,
"max": 10,
"integer": true
}
},
"name": "Test Item"
}
}
}"#);
let result = jgd.generate();
assert!(result.is_ok());
if let Ok(result) = result {
assert!(result.is_array());
if let Value::Array(arr) = result {
assert_eq!(arr.len(), 2);
for item in &arr {
assert!(item.is_object());
if let Value::Object(obj) = item {
assert!(obj.contains_key("id"));
assert!(obj.contains_key("name"));
assert_eq!(obj.get("name"), Some(&Value::String("Test Item".to_string())));
}
}
}
}
}
#[test]
fn test_entities_with_cross_references() {
let jgd = Jgd::from(r#"{
"$format": "jgd/v1",
"version": "1.0.0",
"seed": 42,
"entities": {
"users": {
"fields": {
"id": 1,
"name": "Alice"
}
},
"posts": {
"fields": {
"author_id": {
"ref": "users.id"
},
"title": "My Post"
}
}
}
}"#);
let result = jgd.generate();
assert!(result.is_ok());
if let Ok(result) = result {
assert!(result.is_object());
if let Value::Object(obj) = result {
assert!(obj.contains_key("users"));
assert!(obj.contains_key("posts"));
if let Some(Value::Object(posts)) = obj.get("posts") {
assert_eq!(posts.get("author_id"), Some(&Value::Number(serde_json::Number::from(1))));
assert_eq!(posts.get("title"), Some(&Value::String("My Post".to_string())));
}
}
}
}
#[test]
fn test_custom_locale() {
let jgd = Jgd::from(r#"{
"$format": "jgd/v1",
"version": "1.0.0",
"defaultLocale": "DE",
"root": {
"fields": {
"message": "Hallo Welt"
}
}
}"#);
assert_eq!(jgd.default_locale, "DE");
let result = jgd.generate();
assert!(result.is_ok());
if let Ok(result) = result {
assert!(result.is_object());
}
}
#[test]
fn test_minimal_valid_schema() {
let jgd = Jgd::from(r#"{
"$format": "jgd/v1",
"version": "",
"root": {
"fields": {}
}
}"#);
let result = jgd.generate();
assert!(result.is_ok());
if let Ok(result) = result {
assert!(result.is_object());
if let Value::Object(obj) = result {
assert!(obj.is_empty());
}
}
}
#[test]
fn test_custom_key() {
let key = "custom";
Jgd::add_custom_key(key, Arc::new(|_| Ok(Value::String("worked".to_string()))));
if let Some(func) = Jgd::get_custom_key(key) {
if let Ok(Value::String(value)) = func(Arguments::None) {
assert_eq!("worked", value)
}
}
}
}