relateby-pattern 0.4.2

Core pattern data structures
Documentation
//! Equivalence checking utilities for comparing pattern-rs and gram-hs implementations
//!
//! # Using gramref CLI for Reference Outputs
//!
//! The `gramref` CLI tool can be used to get reference outputs for comparison:
//!
//! ```bash
//! # Get reference output (value only, canonical format)
//! echo '(node1)' | gramref parse --format json --value-only --canonical
//! ```
//!
//! See [gramref CLI Testing Guide](../../../../docs/gramref-cli-testing-guide.md) for
//! comprehensive usage examples and integration patterns.

use serde::{Deserialize, Serialize};
use std::fmt::Debug;

/// Result of an equivalence check between pattern-rs and gram-hs implementations
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EquivalenceResult {
    /// Whether the outputs are equivalent
    pub equivalent: bool,
    /// List of differences found (if not equivalent)
    pub differences: Vec<Difference>,
    /// Method used for comparison
    pub comparison_method: ComparisonMethod,
}

/// A single difference found during equivalence checking
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Difference {
    /// Path to the field that differs
    pub path: Vec<String>,
    /// Expected value (from gram-hs)
    pub expected: String,
    /// Actual value (from pattern-rs)
    pub actual: String,
    /// Description of the difference
    pub description: String,
}

/// Method used for equivalence comparison
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ComparisonMethod {
    /// Direct comparison using PartialEq
    Direct,
    /// JSON serialization comparison
    Json,
    /// Test data comparison
    TestData,
}

/// Options for equivalence checking
#[derive(Debug, Clone)]
pub struct EquivalenceOptions {
    /// Whether to use approximate equality for floating-point values
    pub approximate_float_equality: bool,
    /// Tolerance for floating-point comparisons
    pub float_tolerance: f64,
    /// Fields to ignore during comparison
    pub ignore_fields: Vec<String>,
    /// Comparison method to use
    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,
        }
    }
}

/// Check equivalence between pattern-rs and gram-hs outputs
///
/// # Arguments
///
/// * `gram_rs_output` - Output from pattern-rs implementation
/// * `gram_hs_output` - Output from gram-hs implementation (or test data)
/// * `options` - Comparison options
///
/// # Returns
///
/// `EquivalenceResult` containing comparison results
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 => {
            // Use JSON serialization for detailed comparison
            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 => {
            // For test data comparison, use direct comparison
            // Full test data comparison will be implemented when test case structure is finalized
            check_equivalence(
                gram_rs_output,
                gram_hs_output,
                &EquivalenceOptions {
                    comparison_method: ComparisonMethod::Direct,
                    ..options.clone()
                },
            )
        }
    }
}

/// Check equivalence using extracted test data from gram-hs
///
/// # Arguments
///
/// * `test_case` - Test case from extracted gram-hs data
/// * `gram_rs_impl` - Function that executes pattern-rs implementation
/// * `options` - Comparison options
///
/// # Returns
///
/// `EquivalenceResult` containing comparison results
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,
{
    // Placeholder implementation
    // Will be fully implemented when test case structure is defined
    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,
    }
}

/// Test case structure (placeholder - will match test-sync-format.md from feature 002)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestCase {
    pub name: String,
    pub description: String,
    pub input: TestCaseInput,
    pub expected: TestCaseOutput,
}

/// Test case input (placeholder)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestCaseInput {
    pub r#type: String,
    pub value: serde_json::Value,
}

/// Test case output (placeholder)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestCaseOutput {
    pub r#type: String,
    pub value: serde_json::Value,
}