use crate::{Path, SequenceDiffAlgorithm};
use glob::Pattern;
use serde_value::Value;
use std::rc::Rc;
pub type Comparator = Rc<dyn Fn(&Value, &Value) -> bool>;
#[derive(Clone)]
pub struct DiffConfig {
ignore_paths: Vec<Pattern>,
mask_paths: Vec<Pattern>,
float_tolerances: Vec<(Pattern, f64)>,
default_float_tolerance: Option<f64>,
max_depth: Option<usize>,
collection_limit: usize,
sequence_algorithms: Vec<(Pattern, SequenceDiffAlgorithm)>,
default_sequence_algorithm: SequenceDiffAlgorithm,
comparators: Vec<(Pattern, Comparator)>,
}
impl DiffConfig {
pub fn new() -> Self {
Self::default()
}
pub fn ignore(mut self, pattern: &str) -> Self {
if let Ok(p) = Pattern::new(pattern) {
self.ignore_paths.push(p);
}
self
}
pub fn mask(mut self, pattern: &str) -> Self {
if let Ok(p) = Pattern::new(pattern) {
self.mask_paths.push(p);
}
self
}
pub fn float_tolerance(mut self, pattern: &str, tolerance: f64) -> Self {
if let Ok(p) = Pattern::new(pattern) {
self.float_tolerances.push((p, tolerance));
}
self
}
pub fn default_float_tolerance(mut self, tolerance: f64) -> Self {
self.default_float_tolerance = Some(tolerance);
self
}
pub fn max_depth(mut self, depth: usize) -> Self {
self.max_depth = Some(depth);
self
}
pub fn collection_limit(mut self, limit: usize) -> Self {
self.collection_limit = limit;
self
}
pub fn sequence_algorithm(mut self, pattern: &str, algorithm: SequenceDiffAlgorithm) -> Self {
if let Ok(p) = Pattern::new(pattern) {
self.sequence_algorithms.push((p, algorithm));
}
self
}
pub fn default_sequence_algorithm(mut self, algorithm: SequenceDiffAlgorithm) -> Self {
self.default_sequence_algorithm = algorithm;
self
}
pub(crate) fn should_ignore(&self, path: &Path) -> bool {
let path_str = path.as_str();
self.ignore_paths.iter().any(|p| p.matches(path_str))
}
#[allow(dead_code)] pub(crate) fn should_mask(&self, path: &Path) -> bool {
let path_str = path.as_str();
self.mask_paths.iter().any(|p| p.matches(path_str))
}
pub(crate) fn float_tolerance_for(&self, path: &Path) -> Option<f64> {
let path_str = path.as_str();
for (pattern, tolerance) in &self.float_tolerances {
if pattern.matches(path_str) {
return Some(*tolerance);
}
}
self.default_float_tolerance
}
pub(crate) fn exceeds_depth(&self, path: &Path) -> bool {
if let Some(max) = self.max_depth {
path.depth() > max
} else {
false
}
}
pub(crate) fn get_collection_limit(&self) -> usize {
self.collection_limit
}
pub(crate) fn get_sequence_algorithm(&self, path: &Path) -> SequenceDiffAlgorithm {
let path_str = path.as_str();
for (pattern, algorithm) in &self.sequence_algorithms {
if pattern.matches(path_str) {
return *algorithm;
}
}
self.default_sequence_algorithm
}
pub fn comparator(mut self, pattern: &str, comparator: Comparator) -> Self {
if let Ok(p) = Pattern::new(pattern) {
self.comparators.push((p, comparator));
}
self
}
pub(crate) fn get_comparator(&self, path: &Path) -> Option<&Comparator> {
let path_str = path.as_str();
for (pattern, comparator) in &self.comparators {
if pattern.matches(path_str) {
return Some(comparator);
}
}
None
}
}
impl std::fmt::Debug for DiffConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DiffConfig")
.field("ignore_paths", &self.ignore_paths.len())
.field("mask_paths", &self.mask_paths.len())
.field("float_tolerances", &self.float_tolerances.len())
.field("default_float_tolerance", &self.default_float_tolerance)
.field("max_depth", &self.max_depth)
.field("collection_limit", &self.collection_limit)
.field("sequence_algorithms", &self.sequence_algorithms.len())
.field(
"default_sequence_algorithm",
&self.default_sequence_algorithm,
)
.field("comparators", &self.comparators.len())
.finish()
}
}
impl Default for DiffConfig {
fn default() -> Self {
Self {
ignore_paths: vec![],
mask_paths: vec![],
float_tolerances: vec![],
default_float_tolerance: None,
max_depth: Some(64),
collection_limit: 1000,
sequence_algorithms: vec![],
default_sequence_algorithm: SequenceDiffAlgorithm::IndexBased,
comparators: vec![],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = DiffConfig::default();
assert_eq!(config.max_depth, Some(64));
assert_eq!(config.collection_limit, 1000);
}
#[test]
fn test_ignore() {
let config = DiffConfig::new().ignore("*.temp");
let path = Path::root().field("foo").field("temp");
assert!(config.should_ignore(&path));
}
#[test]
fn test_mask() {
let config = DiffConfig::new().mask("*.password");
let path = Path::root().field("user").field("password");
assert!(config.should_mask(&path));
}
#[test]
fn test_float_tolerance() {
let config = DiffConfig::new()
.float_tolerance("metrics.*", 1e-6)
.default_float_tolerance(1e-9);
let metrics_path = Path::root().field("metrics").field("value");
assert_eq!(config.float_tolerance_for(&metrics_path), Some(1e-6));
let other_path = Path::root().field("other");
assert_eq!(config.float_tolerance_for(&other_path), Some(1e-9));
}
#[test]
fn test_max_depth() {
let config = DiffConfig::new().max_depth(2);
let shallow = Path::root().field("a");
assert!(!config.exceeds_depth(&shallow));
let deep = Path::root().field("a").field("b").field("c");
assert!(config.exceeds_depth(&deep));
}
#[test]
fn test_collection_limit() {
let config = DiffConfig::new().collection_limit(100);
assert_eq!(config.get_collection_limit(), 100);
}
#[test]
fn test_builder_pattern() {
let config = DiffConfig::new()
.mask("*.secret")
.ignore("*.internal")
.float_tolerance("*.value", 0.001)
.max_depth(32)
.collection_limit(500);
assert_eq!(config.max_depth, Some(32));
assert_eq!(config.collection_limit, 500);
}
}