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::{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.0.push(PathSegment::ArrayIndex(Some(index)));
118    }
119
120    /// Push a tuple index to the path.
121    pub fn push_path_tuple_index(&mut self, index: u8) {
122        self.path.0.push(PathSegment::TupleIndex(index));
123    }
124
125    /// Push an extension to the path.
126    pub fn push_path_extension(&mut self, ident: Identifier) {
127        self.path.0.push(PathSegment::Extension(ident));
128    }
129
130    /// Pop the last segment from the path.
131    pub fn pop_path(&mut self) {
132        self.path.0.pop();
133    }
134
135    /// Clone for fork (trial validation).
136    pub fn fork(&self) -> Self {
137        Self {
138            path: self.path.clone(),
139            has_holes: self.has_holes,
140            errors: Vec::new(),
141            warnings: Vec::new(),
142            variant_errors: Vec::new(), // Don't inherit variant errors
143        }
144    }
145
146    /// Merge results from a forked state.
147    pub fn merge(&mut self, other: Self) {
148        self.has_holes |= other.has_holes;
149        self.errors.extend(other.errors);
150        self.warnings.extend(other.warnings);
151    }
152
153    /// Consume and produce final output.
154    pub fn finish(self) -> ValidationOutput {
155        ValidationOutput {
156            is_valid: self.errors.is_empty(),
157            is_complete: self.errors.is_empty() && !self.has_holes,
158            errors: self.errors,
159            warnings: self.warnings,
160        }
161    }
162
163    // -------------------------------------------------------------------------
164    // Variant error collection (for union validation)
165    // -------------------------------------------------------------------------
166
167    /// Record variant attempt errors (for union validation).
168    pub fn record_variant_errors(
169        &mut self,
170        variant_name: String,
171        schema_id: SchemaNodeId,
172        errors: Vec<ValidationError>,
173    ) {
174        self.variant_errors.push((variant_name, schema_id, errors));
175    }
176
177    /// Take collected variant errors (for union error construction).
178    pub fn take_variant_errors(&mut self) -> Vec<(String, SchemaNodeId, Vec<ValidationError>)> {
179        std::mem::take(&mut self.variant_errors)
180    }
181
182    /// Clear variant errors without taking (for successful variant match).
183    pub fn clear_variant_errors(&mut self) {
184        self.variant_errors.clear();
185    }
186}
187
188// =============================================================================
189// ValidationContext (shared immutable + RefCell for state)
190// =============================================================================
191
192/// Validation context combining schema reference and mutable state.
193///
194/// Uses interior mutability (RefCell) to allow validators implementing
195/// `DocumentParser` to record errors without requiring `&mut self`.
196pub struct ValidationContext<'a> {
197    /// Reference to the schema being validated against
198    pub schema: &'a SchemaDocument,
199    /// Reference to the document being validated
200    pub document: &'a EureDocument,
201    /// Mutable state (errors, warnings, path, holes)
202    pub state: RefCell<ValidationState>,
203}
204
205impl<'a> ValidationContext<'a> {
206    /// Create a new validation context.
207    pub fn new(document: &'a EureDocument, schema: &'a SchemaDocument) -> Self {
208        Self {
209            schema,
210            document,
211            state: RefCell::new(ValidationState::new()),
212        }
213    }
214
215    /// Create a context with existing state (for fork/merge).
216    pub fn with_state(
217        document: &'a EureDocument,
218        schema: &'a SchemaDocument,
219        state: ValidationState,
220    ) -> Self {
221        Self {
222            schema,
223            document,
224            state: RefCell::new(state),
225        }
226    }
227
228    /// Record an error.
229    pub fn record_error(&self, error: ValidationError) {
230        self.state.borrow_mut().record_error(error);
231    }
232
233    /// Record a warning.
234    pub fn record_warning(&self, warning: ValidationWarning) {
235        self.state.borrow_mut().record_warning(warning);
236    }
237
238    /// Mark that a hole was encountered.
239    pub fn mark_has_holes(&self) {
240        self.state.borrow_mut().mark_has_holes();
241    }
242
243    /// Check if any errors have been recorded.
244    pub fn has_errors(&self) -> bool {
245        self.state.borrow().has_errors()
246    }
247
248    /// Get the current error count.
249    pub fn error_count(&self) -> usize {
250        self.state.borrow().error_count()
251    }
252
253    /// Get a clone of the current path.
254    pub fn path(&self) -> EurePath {
255        self.state.borrow().path.clone()
256    }
257
258    /// Push an identifier to the path.
259    pub fn push_path_ident(&self, ident: Identifier) {
260        self.state.borrow_mut().push_path_ident(ident);
261    }
262
263    /// Push a key to the path.
264    pub fn push_path_key(&self, key: ObjectKey) {
265        self.state.borrow_mut().push_path_key(key);
266    }
267
268    /// Push an array index to the path.
269    pub fn push_path_index(&self, index: usize) {
270        self.state.borrow_mut().push_path_index(index);
271    }
272
273    /// Push a tuple index to the path.
274    pub fn push_path_tuple_index(&self, index: u8) {
275        self.state.borrow_mut().push_path_tuple_index(index);
276    }
277
278    /// Push an extension to the path.
279    pub fn push_path_extension(&self, ident: Identifier) {
280        self.state.borrow_mut().push_path_extension(ident);
281    }
282
283    /// Pop the last segment from the path.
284    pub fn pop_path(&self) {
285        self.state.borrow_mut().pop_path();
286    }
287
288    /// Fork for trial validation (returns forked state).
289    pub fn fork_state(&self) -> ValidationState {
290        self.state.borrow().fork()
291    }
292
293    /// Merge forked state back.
294    pub fn merge_state(&self, other: ValidationState) {
295        self.state.borrow_mut().merge(other);
296    }
297
298    /// Resolve type references to get the actual schema content.
299    pub fn resolve_schema_content(&self, schema_id: SchemaNodeId) -> &SchemaNodeContent {
300        let mut current_id = schema_id;
301        // Limit iterations to prevent infinite loops with circular refs
302        for _ in 0..100 {
303            let content = &self.schema.node(current_id).content;
304            match content {
305                SchemaNodeContent::Reference(type_ref) => {
306                    if type_ref.namespace.is_some() {
307                        return content; // Cross-schema refs not resolved
308                    }
309                    if let Some(&resolved_id) = self.schema.types.get(&type_ref.name) {
310                        current_id = resolved_id;
311                    } else {
312                        return content; // Unresolved reference
313                    }
314                }
315                _ => return content,
316            }
317        }
318        &self.schema.node(current_id).content
319    }
320
321    /// Create a ParseContext for the given node.
322    pub fn parse_context(
323        &self,
324        node_id: eure_document::document::NodeId,
325    ) -> eure_document::parse::ParseContext<'a> {
326        eure_document::parse::ParseContext::new(self.document, node_id)
327    }
328
329    // -------------------------------------------------------------------------
330    // Variant error collection (for union validation)
331    // -------------------------------------------------------------------------
332
333    /// Record variant attempt errors during union validation.
334    pub fn record_variant_errors(
335        &self,
336        variant_name: String,
337        schema_id: SchemaNodeId,
338        errors: Vec<ValidationError>,
339    ) {
340        self.state
341            .borrow_mut()
342            .record_variant_errors(variant_name, schema_id, errors);
343    }
344
345    /// Take collected variant errors.
346    pub fn take_variant_errors(&self) -> Vec<(String, SchemaNodeId, Vec<ValidationError>)> {
347        self.state.borrow_mut().take_variant_errors()
348    }
349
350    /// Clear variant errors.
351    pub fn clear_variant_errors(&self) {
352        self.state.borrow_mut().clear_variant_errors();
353    }
354
355    /// Consume context and produce final output.
356    pub fn finish(self) -> ValidationOutput {
357        self.state.into_inner().finish()
358    }
359}