use serde::Deserialize; use std::fs; #[derive(Debug, Deserialize)] struct TestSuite { #[allow(dead_code)] description: String, schema: Option, // Support JSPG-style test suites with explicit types/enums/puncs types: Option, enums: Option, puncs: Option, tests: Vec, } #[derive(Debug, Deserialize)] struct TestCase { description: String, data: serde_json::Value, valid: bool, // Support explicit schema ID target for test case schema_id: Option, // Expected output for masking tests #[allow(dead_code)] expected: Option, } // use crate::registry::REGISTRY; // No longer used directly for tests! use crate::validator::Validator; use serde_json::Value; pub fn deserialize_some<'de, D>(deserializer: D) -> Result, D::Error> where D: serde::Deserializer<'de>, { let v = Value::deserialize(deserializer)?; Ok(Some(v)) } pub fn run_test_file_at_index(path: &str, index: usize) -> Result<(), String> { let content = fs::read_to_string(path).unwrap_or_else(|_| panic!("Failed to read file: {}", path)); let suite: Vec = serde_json::from_str(&content) .unwrap_or_else(|e| panic!("Failed to parse JSON in {}: {}", path, e)); if index >= suite.len() { panic!("Index {} out of bounds for file {}", index, path); } let group = &suite[index]; let mut failures = Vec::::new(); // Create Local Registry for this test group let mut registry = crate::registry::Registry::new(); // Helper to register items with 'schemas' let register_schemas = |registry: &mut crate::registry::Registry, items_val: Option<&Value>| { if let Some(val) = items_val { if let Value::Array(arr) = val { for item in arr { if let Some(schemas_val) = item.get("schemas") { if let Value::Array(schemas) = schemas_val { for schema_val in schemas { if let Ok(schema) = serde_json::from_value::(schema_val.clone()) { registry.add(schema); } } } } } } } }; // 1. Register Family Schemas if 'types' is present if let Some(types_val) = &group.types { if let Value::Array(arr) = types_val { let mut family_map: std::collections::HashMap> = std::collections::HashMap::new(); for item in arr { if let Some(name) = item.get("name").and_then(|v| v.as_str()) { if let Some(hierarchy) = item.get("hierarchy").and_then(|v| v.as_array()) { for ancestor in hierarchy { if let Some(anc_str) = ancestor.as_str() { family_map .entry(anc_str.to_string()) .or_default() .insert(name.to_string()); } } } } } for (family_name, members) in family_map { let id = format!("{}.family", family_name); let object_refs: Vec = members .iter() .map(|s| serde_json::json!({ "$ref": s })) .collect(); let schema_json = serde_json::json!({ "$id": id, "oneOf": object_refs }); if let Ok(schema) = serde_json::from_value::(schema_json) { registry.add(schema); } } } } // 2. Register items directly register_schemas(&mut registry, group.enums.as_ref()); register_schemas(&mut registry, group.types.as_ref()); register_schemas(&mut registry, group.puncs.as_ref()); // 3. Register root 'schemas' if present (generic test support) // Some tests use a raw 'schema' or 'schemas' field at the group level if let Some(schema_val) = &group.schema { match serde_json::from_value::(schema_val.clone()) { Ok(mut schema) => { let id_clone = schema.obj.id.clone(); if id_clone.is_some() { registry.add(schema); } else { // Fallback ID if none provided in schema let id = format!("test:{}:{}", path, index); schema.obj.id = Some(id); registry.add(schema); } } Err(e) => { eprintln!( "DEBUG: FAILED to deserialize group schema for index {}: {}", index, e ); } } } // Create Validator Instance (Takes ownership of registry) let validator = Validator::new(registry); // 4. Run Tests for (_test_index, test) in group.tests.iter().enumerate() { let mut schema_id = test.schema_id.clone(); // If no explicit schema_id, try to infer from the single schema in the group if schema_id.is_none() { if let Some(s) = &group.schema { // If 'schema' is a single object, use its ID or "root" if let Some(obj) = s.as_object() { if let Some(id_val) = obj.get("$id") { schema_id = id_val.as_str().map(|s| s.to_string()); } } if schema_id.is_none() { schema_id = Some(format!("test:{}:{}", path, index)); } } } // Default to the first punc if present (for puncs.json style) if schema_id.is_none() { if let Some(Value::Array(puncs)) = &group.puncs { if let Some(first_punc) = puncs.first() { if let Some(Value::Array(schemas)) = first_punc.get("schemas") { if let Some(first_schema) = schemas.first() { if let Some(id) = first_schema.get("$id").and_then(|v| v.as_str()) { schema_id = Some(id.to_string()); } } } } } } if let Some(sid) = schema_id { let result = validator.validate(&sid, &test.data); if !result.errors.is_empty() != !test.valid { failures.push(format!( "[{}] Test '{}' failed. Expected: {}, Got: {}. Errors: {:?}", group.description, test.description, test.valid, !result.errors.is_empty(), // "Got Invalid?" result.errors )); } } else { failures.push(format!( "[{}] Test '{}' skipped: No schema ID found.", group.description, test.description )); } } if !failures.is_empty() { return Err(failures.join("\n")); } Ok(()) } pub fn run_test_file(path: &str) -> Result<(), String> { let content = fs::read_to_string(path).unwrap_or_else(|_| panic!("Failed to read file: {}", path)); let suite: Vec = serde_json::from_str(&content) .unwrap_or_else(|e| panic!("Failed to parse JSON in {}: {}", path, e)); let mut failures = Vec::::new(); for (group_index, group) in suite.into_iter().enumerate() { // Create Isolated Registry for this test group let mut registry = crate::registry::Registry::new(); // Helper to register items with 'schemas' let register_schemas = |registry: &mut crate::registry::Registry, items_val: Option| { if let Some(val) = items_val { if let Value::Array(arr) = val { for item in arr { if let Some(schemas_val) = item.get("schemas") { if let Value::Array(schemas) = schemas_val { for schema_val in schemas { if let Ok(schema) = serde_json::from_value::(schema_val.clone()) { registry.add(schema); } } } } } } } }; // 1. Register Family Schemas if 'types' is present if let Some(types_val) = &group.types { if let Value::Array(arr) = types_val { let mut family_map: std::collections::HashMap> = std::collections::HashMap::new(); for item in arr { if let Some(name) = item.get("name").and_then(|v| v.as_str()) { // Default hierarchy contains self if not specified? // Usually hierarchy is explicit in these tests. if let Some(hierarchy) = item.get("hierarchy").and_then(|v| v.as_array()) { for ancestor in hierarchy { if let Some(anc_str) = ancestor.as_str() { family_map .entry(anc_str.to_string()) .or_default() .insert(name.to_string()); } } } } } for (family_name, members) in family_map { let id = format!("{}.family", family_name); let object_refs: Vec = members .into_iter() .map(|s| serde_json::json!({ "$ref": s })) .collect(); let schema_json = serde_json::json!({ "$id": id, "oneOf": object_refs }); if let Ok(schema) = serde_json::from_value::(schema_json) { registry.add(schema); } } } } // Register 'types', 'enums', and 'puncs' if present (JSPG style) register_schemas(&mut registry, group.types); register_schemas(&mut registry, group.enums); register_schemas(&mut registry, group.puncs); // Register main 'schema' if present (Standard style) // Ensure ID is a valid URI to avoid Url::parse errors in Compiler let unique_id = format!("test:{}:{}", path, group_index); // Register main 'schema' if present (Standard style) if let Some(ref schema_val) = group.schema { let mut schema: crate::schema::Schema = serde_json::from_value(schema_val.clone()).expect("Failed to parse test schema"); // If schema has no ID, assign unique_id and use add() or manual insert? // Compiler needs ID. Registry::add needs ID. if schema.obj.id.is_none() { schema.obj.id = Some(unique_id.clone()); } registry.add(schema); } // Create Instance (Takes Ownership) let validator = Validator::new(registry); for test in group.tests { // Use explicit schema_id from test, or default to unique_id let schema_id = test.schema_id.as_deref().unwrap_or(&unique_id).to_string(); let drop = validator.validate(&schema_id, &test.data); if test.valid { if !drop.errors.is_empty() { let msg = format!( "Test failed (expected valid): {}\nSchema: {:?}\nData: {:?}\nErrors: {:?}", test.description, group.schema, // We might need to find the actual schema used if schema_id is custom test.data, drop.errors ); eprintln!("{}", msg); failures.push(msg); } } else { if drop.errors.is_empty() { let msg = format!( "Test failed (expected invalid): {}\nSchema: {:?}\nData: {:?}\nErrors: (Empty)", test.description, group.schema, test.data ); println!("{}", msg); failures.push(msg); } } } } if !failures.is_empty() { return Err(format!( "{} tests failed in file {}:\n\n{}", failures.len(), path, failures.join("\n\n") )); } Ok(()) } pub fn is_integer(v: &Value) -> bool { match v { Value::Number(n) => { n.is_i64() || n.is_u64() || n.as_f64().filter(|n| n.fract() == 0.0).is_some() } _ => false, } } /// serde_json treats 0 and 0.0 not equal. so we cannot simply use v1==v2 pub fn equals(v1: &Value, v2: &Value) -> bool { // eprintln!("Comparing {:?} with {:?}", v1, v2); match (v1, v2) { (Value::Null, Value::Null) => true, (Value::Bool(b1), Value::Bool(b2)) => b1 == b2, (Value::Number(n1), Value::Number(n2)) => { if let (Some(n1), Some(n2)) = (n1.as_u64(), n2.as_u64()) { return n1 == n2; } if let (Some(n1), Some(n2)) = (n1.as_i64(), n2.as_i64()) { return n1 == n2; } if let (Some(n1), Some(n2)) = (n1.as_f64(), n2.as_f64()) { return (n1 - n2).abs() < f64::EPSILON; } false } (Value::String(s1), Value::String(s2)) => s1 == s2, (Value::Array(arr1), Value::Array(arr2)) => { if arr1.len() != arr2.len() { return false; } arr1.iter().zip(arr2).all(|(e1, e2)| equals(e1, e2)) } (Value::Object(obj1), Value::Object(obj2)) => { if obj1.len() != obj2.len() { return false; } for (k1, v1) in obj1 { if let Some(v2) = obj2.get(k1) { if !equals(v1, v2) { return false; } } else { return false; } } true } _ => false, } }