Skip to main content

json_eval_rs/jsoneval/
validation_cache.rs

1use crate::jsoneval::types::{ValidationError, ValidationResult};
2use indexmap::IndexMap;
3use serde_json::Value;
4
5/// Cached validation result for a single field
6#[derive(Clone, Debug)]
7pub struct CachedFieldValidation {
8    /// Field value at last validation
9    pub field_data: Value,
10    /// Visibility state at last validation
11    pub is_hidden: bool,
12    /// Whether readonly fields were validated
13    pub validate_readonly: bool,
14    /// Rule evaluation snapshot (to detect changes in dynamic $evaluation rules)
15    pub rules_snapshot: Value,
16    /// Cached error if invalid, None if valid
17    pub error: Option<ValidationError>,
18}
19
20/// Cache for validation results (whole-form and per-field)
21#[derive(Clone, Default, Debug)]
22pub struct ValidationCache {
23    /// Last input data string for whole-form caching
24    pub last_data_str: Option<String>,
25    /// Last context string for whole-form caching
26    pub last_context_str: Option<String>,
27    /// Whether last whole-form validation validated readonly fields
28    pub last_validate_readonly: bool,
29    /// Last full validation result
30    pub last_result: Option<ValidationResult>,
31    /// Per-field validation cache
32    pub field_cache: IndexMap<String, CachedFieldValidation>,
33}
34
35impl ValidationCache {
36    pub fn new() -> Self {
37        Self::default()
38    }
39
40    /// Check if the full validation result can be returned immediately
41    #[inline]
42    pub fn get_cached_full_result(
43        &self,
44        data: &str,
45        context: Option<&str>,
46        validate_readonly: bool,
47    ) -> Option<ValidationResult> {
48        if self.last_validate_readonly != validate_readonly {
49            return None;
50        }
51
52        let last_data = self.last_data_str.as_deref()?;
53        if last_data != data {
54            return None;
55        }
56
57        let ctx_matches = match (self.last_context_str.as_deref(), context) {
58            (None, None) => true,
59            (Some(""), None) | (None, Some("")) => true,
60            (Some("{}"), None) | (None, Some("{}")) => true,
61            (Some(a), Some(b)) => a == b,
62            _ => false,
63        };
64
65        if ctx_matches {
66            self.last_result.clone()
67        } else {
68            None
69        }
70    }
71
72    /// Check if a single field has a valid cache entry matching current state
73    #[inline]
74    pub fn check_field_cache(
75        &self,
76        field_path: &str,
77        field_data: &Value,
78        is_hidden: bool,
79        validate_readonly: bool,
80        rules: &Value,
81    ) -> Option<Option<ValidationError>> {
82        let cached = self.field_cache.get(field_path)?;
83        if cached.is_hidden == is_hidden
84            && cached.validate_readonly == validate_readonly
85            && &cached.field_data == field_data
86            && &cached.rules_snapshot == rules
87        {
88            Some(cached.error.clone())
89        } else {
90            None
91        }
92    }
93
94    /// Update cache for a single field
95    #[inline]
96    pub fn update_field(
97        &mut self,
98        field_path: String,
99        field_data: Value,
100        is_hidden: bool,
101        validate_readonly: bool,
102        rules_snapshot: Value,
103        error: Option<ValidationError>,
104    ) {
105        self.field_cache.insert(
106            field_path,
107            CachedFieldValidation {
108                field_data,
109                is_hidden,
110                validate_readonly,
111                rules_snapshot,
112                error,
113            },
114        );
115    }
116
117    /// Save full validation result
118    #[inline]
119    pub fn save_full_result(
120        &mut self,
121        data_str: String,
122        context_str: Option<String>,
123        validate_readonly: bool,
124        result: ValidationResult,
125    ) {
126        self.last_data_str = Some(data_str);
127        self.last_context_str = context_str;
128        self.last_validate_readonly = validate_readonly;
129        self.last_result = Some(result);
130    }
131
132    /// Invalidate whole-result cache (e.g. on partial path validation)
133    #[inline]
134    pub fn invalidate_full_result(&mut self) {
135        self.last_data_str = None;
136        self.last_context_str = None;
137        self.last_validate_readonly = false;
138        self.last_result = None;
139    }
140
141    /// Clear all cached validation state
142    #[inline]
143    pub fn clear(&mut self) {
144        self.last_data_str = None;
145        self.last_context_str = None;
146        self.last_validate_readonly = false;
147        self.last_result = None;
148        self.field_cache.clear();
149    }
150}