Skip to main content

eure_schema/validate/
context.rs

1//! Validation context and output types
2//!
3//! `ValidationContext` manages state during validation:
4//! - Schema reference
5//! - Current path for error reporting
6//! - Accumulated errors and warnings
7//! - Hole tracking for completeness check
8
9use std::cell::RefCell;
10
11use eure_document::document::EureDocument;
12use eure_document::identifier::Identifier;
13use eure_document::path::{ArrayIndexKind, EurePath, PathSegment};
14use eure_document::value::ObjectKey;
15
16use crate::{SchemaDocument, SchemaNodeContent, SchemaNodeId};
17
18use super::error::{ValidationError, ValidationWarning};
19
20// =============================================================================
21// ValidationOutput (final result for public API)
22// =============================================================================
23
24/// Final validation output returned to callers.
25#[derive(Debug, Clone, Default)]
26pub struct ValidationOutput {
27    /// No type errors (holes are allowed)
28    pub is_valid: bool,
29    /// No type errors AND no holes
30    pub is_complete: bool,
31    /// Type errors encountered during validation
32    pub errors: Vec<ValidationError>,
33    /// Warnings (e.g., unknown extensions)
34    pub warnings: Vec<ValidationWarning>,
35}
36
37// =============================================================================
38// ValidationState (internal mutable state)
39// =============================================================================
40
41/// Internal mutable state during validation.
42///
43/// Wrapped in RefCell to allow interior mutability through shared references.
44#[derive(Debug)]
45pub struct ValidationState {
46    /// Current path in the document (for error reporting)
47    pub path: EurePath,
48    /// Whether any holes have been encountered
49    pub has_holes: bool,
50    /// Accumulated validation errors
51    pub errors: Vec<ValidationError>,
52    /// Accumulated warnings
53    pub warnings: Vec<ValidationWarning>,
54    /// Temporary storage for variant errors during union validation.
55    /// Each entry is (variant_name, variant_schema_id, errors_from_that_variant).
56    pub(crate) variant_errors: Vec<(String, SchemaNodeId, Vec<ValidationError>)>,
57}
58
59impl Default for ValidationState {
60    fn default() -> Self {
61        Self {
62            path: EurePath::root(),
63            has_holes: false,
64            errors: Vec::new(),
65            warnings: Vec::new(),
66            variant_errors: Vec::new(),
67        }
68    }
69}
70
71impl ValidationState {
72    pub fn new() -> Self {
73        Self::default()
74    }
75
76    /// Record an error (validation continues).
77    pub fn record_error(&mut self, error: ValidationError) {
78        self.errors.push(error);
79    }
80
81    /// Record a warning.
82    pub fn record_warning(&mut self, warning: ValidationWarning) {
83        self.warnings.push(warning);
84    }
85
86    /// Mark that a hole was encountered.
87    pub fn mark_has_holes(&mut self) {
88        self.has_holes = true;
89    }
90
91    /// Check if any errors have been recorded.
92    pub fn has_errors(&self) -> bool {
93        !self.errors.is_empty()
94    }
95
96    /// Get the number of errors.
97    pub fn error_count(&self) -> usize {
98        self.errors.len()
99    }
100
101    // -------------------------------------------------------------------------
102    // Path management
103    // -------------------------------------------------------------------------
104
105    /// Push an identifier to the path.
106    pub fn push_path_ident(&mut self, ident: Identifier) {
107        self.path.0.push(PathSegment::Ident(ident));
108    }
109
110    /// Push a key to the path.
111    pub fn push_path_key(&mut self, key: ObjectKey) {
112        self.path.0.push(PathSegment::Value(key));
113    }
114
115    /// Push an array index to the path.
116    pub fn push_path_index(&mut self, index: usize) {
117        self.path
118            .0
119            .push(PathSegment::ArrayIndex(ArrayIndexKind::Specific(index)));
120    }
121
122    /// Push a tuple index to the path.
123    pub fn push_path_tuple_index(&mut self, index: u8) {
124        self.path.0.push(PathSegment::TupleIndex(index));
125    }
126
127    /// Push an extension to the path.
128    pub fn push_path_extension(&mut self, ident: Identifier) {
129        self.path.0.push(PathSegment::Extension(ident));
130    }
131
132    /// Pop the last segment from the path.
133    pub fn pop_path(&mut self) {
134        self.path.0.pop();
135    }
136
137    /// Clone for fork (trial validation).
138    pub fn fork(&self) -> Self {
139        Self {
140            path: self.path.clone(),
141            has_holes: self.has_holes,
142            errors: Vec::new(),
143            warnings: Vec::new(),
144            variant_errors: Vec::new(), // Don't inherit variant errors
145        }
146    }
147
148    /// Merge results from a forked state.
149    pub fn merge(&mut self, other: Self) {
150        self.has_holes |= other.has_holes;
151        self.errors.extend(other.errors);
152        self.warnings.extend(other.warnings);
153    }
154
155    /// Consume and produce final output.
156    pub fn finish(self) -> ValidationOutput {
157        ValidationOutput {
158            is_valid: self.errors.is_empty(),
159            is_complete: self.errors.is_empty() && !self.has_holes,
160            errors: self.errors,
161            warnings: self.warnings,
162        }
163    }
164
165    // -------------------------------------------------------------------------
166    // Variant error collection (for union validation)
167    // -------------------------------------------------------------------------
168
169    /// Record variant attempt errors (for union validation).
170    pub fn record_variant_errors(
171        &mut self,
172        variant_name: String,
173        schema_id: SchemaNodeId,
174        errors: Vec<ValidationError>,
175    ) {
176        self.variant_errors.push((variant_name, schema_id, errors));
177    }
178
179    /// Take collected variant errors (for union error construction).
180    pub fn take_variant_errors(&mut self) -> Vec<(String, SchemaNodeId, Vec<ValidationError>)> {
181        std::mem::take(&mut self.variant_errors)
182    }
183
184    /// Clear variant errors without taking (for successful variant match).
185    pub fn clear_variant_errors(&mut self) {
186        self.variant_errors.clear();
187    }
188}
189
190// =============================================================================
191// ValidationContext (shared immutable + RefCell for state)
192// =============================================================================
193
194/// Validation context combining schema reference and mutable state.
195///
196/// Uses interior mutability (RefCell) to allow validators implementing
197/// `DocumentParser` to record errors without requiring `&mut self`.
198pub struct ValidationContext<'a> {
199    /// Reference to the schema being validated against
200    pub schema: &'a SchemaDocument,
201    /// Reference to the document being validated
202    pub document: &'a EureDocument,
203    /// Mutable state (errors, warnings, path, holes)
204    pub state: RefCell<ValidationState>,
205}
206
207impl<'a> ValidationContext<'a> {
208    /// Create a new validation context.
209    pub fn new(document: &'a EureDocument, schema: &'a SchemaDocument) -> Self {
210        Self {
211            schema,
212            document,
213            state: RefCell::new(ValidationState::new()),
214        }
215    }
216
217    /// Create a context with existing state (for fork/merge).
218    pub fn with_state(
219        document: &'a EureDocument,
220        schema: &'a SchemaDocument,
221        state: ValidationState,
222    ) -> Self {
223        Self {
224            schema,
225            document,
226            state: RefCell::new(state),
227        }
228    }
229
230    /// Record an error.
231    pub fn record_error(&self, error: ValidationError) {
232        self.state.borrow_mut().record_error(error);
233    }
234
235    /// Record a warning.
236    pub fn record_warning(&self, warning: ValidationWarning) {
237        self.state.borrow_mut().record_warning(warning);
238    }
239
240    /// Mark that a hole was encountered.
241    pub fn mark_has_holes(&self) {
242        self.state.borrow_mut().mark_has_holes();
243    }
244
245    /// Check if any errors have been recorded.
246    pub fn has_errors(&self) -> bool {
247        self.state.borrow().has_errors()
248    }
249
250    /// Get the current error count.
251    pub fn error_count(&self) -> usize {
252        self.state.borrow().error_count()
253    }
254
255    /// Get a clone of the current path.
256    pub fn path(&self) -> EurePath {
257        self.state.borrow().path.clone()
258    }
259
260    /// Push an identifier to the path.
261    pub fn push_path_ident(&self, ident: Identifier) {
262        self.state.borrow_mut().push_path_ident(ident);
263    }
264
265    /// Push a key to the path.
266    pub fn push_path_key(&self, key: ObjectKey) {
267        self.state.borrow_mut().push_path_key(key);
268    }
269
270    /// Push an array index to the path.
271    pub fn push_path_index(&self, index: usize) {
272        self.state.borrow_mut().push_path_index(index);
273    }
274
275    /// Push a tuple index to the path.
276    pub fn push_path_tuple_index(&self, index: u8) {
277        self.state.borrow_mut().push_path_tuple_index(index);
278    }
279
280    /// Push an extension to the path.
281    pub fn push_path_extension(&self, ident: Identifier) {
282        self.state.borrow_mut().push_path_extension(ident);
283    }
284
285    /// Pop the last segment from the path.
286    pub fn pop_path(&self) {
287        self.state.borrow_mut().pop_path();
288    }
289
290    /// Fork for trial validation (returns forked state).
291    pub fn fork_state(&self) -> ValidationState {
292        self.state.borrow().fork()
293    }
294
295    /// Merge forked state back.
296    pub fn merge_state(&self, other: ValidationState) {
297        self.state.borrow_mut().merge(other);
298    }
299
300    /// Resolve type references to get the actual schema content.
301    pub fn resolve_schema_content(&self, schema_id: SchemaNodeId) -> &SchemaNodeContent {
302        let mut current_id = schema_id;
303        // Limit iterations to prevent infinite loops with circular refs
304        for _ in 0..100 {
305            let content = &self.schema.node(current_id).content;
306            match content {
307                SchemaNodeContent::Reference(type_ref) => {
308                    if type_ref.namespace.is_some() {
309                        return content; // Cross-schema refs not resolved
310                    }
311                    if let Some(&resolved_id) = self.schema.types.get(&type_ref.name) {
312                        current_id = resolved_id;
313                    } else {
314                        return content; // Unresolved reference
315                    }
316                }
317                _ => return content,
318            }
319        }
320        &self.schema.node(current_id).content
321    }
322
323    /// Create a ParseContext for the given node.
324    pub fn parse_context(
325        &self,
326        node_id: eure_document::document::NodeId,
327    ) -> eure_document::parse::ParseContext<'a> {
328        eure_document::parse::ParseContext::new(self.document, node_id)
329    }
330
331    // -------------------------------------------------------------------------
332    // Variant error collection (for union validation)
333    // -------------------------------------------------------------------------
334
335    /// Record variant attempt errors during union validation.
336    pub fn record_variant_errors(
337        &self,
338        variant_name: String,
339        schema_id: SchemaNodeId,
340        errors: Vec<ValidationError>,
341    ) {
342        self.state
343            .borrow_mut()
344            .record_variant_errors(variant_name, schema_id, errors);
345    }
346
347    /// Take collected variant errors.
348    pub fn take_variant_errors(&self) -> Vec<(String, SchemaNodeId, Vec<ValidationError>)> {
349        self.state.borrow_mut().take_variant_errors()
350    }
351
352    /// Clear variant errors.
353    pub fn clear_variant_errors(&self) {
354        self.state.borrow_mut().clear_variant_errors();
355    }
356
357    /// Consume context and produce final output.
358    pub fn finish(self) -> ValidationOutput {
359        self.state.into_inner().finish()
360    }
361}