use serde::{Deserialize, Serialize};
use std::fmt::Debug;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EquivalenceResult {
pub equivalent: bool,
pub differences: Vec<Difference>,
pub comparison_method: ComparisonMethod,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Difference {
pub path: Vec<String>,
pub expected: String,
pub actual: String,
pub description: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ComparisonMethod {
Direct,
Json,
TestData,
}
#[derive(Debug, Clone)]
pub struct EquivalenceOptions {
pub approximate_float_equality: bool,
pub float_tolerance: f64,
pub ignore_fields: Vec<String>,
pub comparison_method: ComparisonMethod,
}
impl Default for EquivalenceOptions {
fn default() -> Self {
Self {
approximate_float_equality: false,
float_tolerance: 1e-6,
ignore_fields: Vec::new(),
comparison_method: ComparisonMethod::Json,
}
}
}
pub fn check_equivalence<T>(
gram_rs_output: &T,
gram_hs_output: &T,
options: &EquivalenceOptions,
) -> EquivalenceResult
where
T: Serialize + PartialEq + Debug,
{
match options.comparison_method {
ComparisonMethod::Direct => {
let equivalent = gram_rs_output == gram_hs_output;
EquivalenceResult {
equivalent,
differences: if equivalent {
Vec::new()
} else {
vec![Difference {
path: vec!["root".to_string()],
expected: format!("{:?}", gram_hs_output),
actual: format!("{:?}", gram_rs_output),
description: "Direct comparison: outputs differ".to_string(),
}]
},
comparison_method: ComparisonMethod::Direct,
}
}
ComparisonMethod::Json => {
let rs_json = serde_json::to_string(gram_rs_output)
.unwrap_or_else(|_| format!("{:?}", gram_rs_output));
let hs_json = serde_json::to_string(gram_hs_output)
.unwrap_or_else(|_| format!("{:?}", gram_hs_output));
let equivalent = rs_json == hs_json;
EquivalenceResult {
equivalent,
differences: if equivalent {
Vec::new()
} else {
vec![Difference {
path: vec!["root".to_string()],
expected: hs_json,
actual: rs_json,
description: "JSON comparison: outputs differ".to_string(),
}]
},
comparison_method: ComparisonMethod::Json,
}
}
ComparisonMethod::TestData => {
check_equivalence(
gram_rs_output,
gram_hs_output,
&EquivalenceOptions {
comparison_method: ComparisonMethod::Direct,
..options.clone()
},
)
}
}
}
pub fn check_equivalence_from_test_data<T, F>(
_test_case: &TestCase,
_gram_rs_impl: F,
_options: &EquivalenceOptions,
) -> EquivalenceResult
where
T: Serialize + PartialEq + Debug,
F: FnOnce(&TestCaseInput) -> T,
{
EquivalenceResult {
equivalent: false,
differences: vec![Difference {
path: vec!["test_case".to_string()],
expected: "test_case.expected".to_string(),
actual: "test_case.actual".to_string(),
description: "Test case comparison not yet implemented".to_string(),
}],
comparison_method: ComparisonMethod::TestData,
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestCase {
pub name: String,
pub description: String,
pub input: TestCaseInput,
pub expected: TestCaseOutput,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestCaseInput {
pub r#type: String,
pub value: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestCaseOutput {
pub r#type: String,
pub value: serde_json::Value,
}