noyalib/cst/document/validation.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2// Copyright (c) 2026 Noyalib. All rights reserved.
3
4//! Typed-cache validation and atomic document-state replacement.
5
6use super::{Document, RepairScope};
7use crate::cst::builder::parse_full;
8#[cfg(test)]
9use crate::error::Error;
10use crate::error::Result;
11
12impl Document {
13 /// Populate the typed cache from `self.source` if it is empty.
14 /// Every public edit validates the complete resulting document
15 /// before committing it, so a failure here indicates an internal
16 /// invariant violation rather than caller-provided YAML.
17 pub(super) fn ensure_cache(&self) {
18 if self.cache.borrow().is_some() {
19 return;
20 }
21 let parsed = crate::parser::parse_exactly_one(&self.source, &self.config)
22 .expect("Document source must always parse — local repair invariant violated");
23 *self.cache.borrow_mut() = Some(parsed);
24 }
25
26 /// Verify that the current source re-parses cleanly.
27 ///
28 /// Public mutators validate the complete result before committing,
29 /// so this method normally returns immediately from the populated
30 /// typed cache. It remains useful as an explicit integrity check for
31 /// callers that accept a `Document` from another component.
32 ///
33 /// # Errors
34 ///
35 /// Returns the underlying parse error if the source no longer
36 /// parses as a single YAML document.
37 ///
38 /// # Examples
39 ///
40 /// A malformed edit is rejected atomically and the original remains
41 /// valid:
42 ///
43 /// ```
44 /// use noyalib::cst::parse_document;
45 ///
46 /// let mut doc = parse_document("name: foo\n").unwrap();
47 /// assert!(doc.set("name", "[").is_err());
48 /// assert!(doc.validate().is_ok());
49 /// assert_eq!(doc.to_string(), "name: foo\n");
50 /// ```
51 ///
52 /// Validate a freshly-parsed document — always succeeds:
53 ///
54 /// ```
55 /// use noyalib::cst::parse_document;
56 ///
57 /// let doc = parse_document("name: foo\n").unwrap();
58 /// assert!(doc.validate().is_ok());
59 /// ```
60 pub fn validate(&self) -> Result<()> {
61 #[cfg(test)]
62 if super::fault::validate_should_fail() {
63 return Err(Error::Parse("injected validate failure".into()));
64 }
65 if self.cache.borrow().is_some() {
66 return Ok(());
67 }
68 let parsed = crate::parser::parse_exactly_one(&self.source, &self.config)?;
69 crate::policy::check_document(&self.config.policies, &parsed.0)?;
70 *self.cache.borrow_mut() = Some(parsed);
71 Ok(())
72 }
73
74 /// Validate `candidate` and atomically replace every document view.
75 pub(super) fn commit_source(
76 &mut self,
77 candidate: &str,
78 repair_scope: RepairScope,
79 ) -> Result<()> {
80 let parsed = parse_full(candidate, &self.config)?;
81 self.source = parsed.source;
82 self.green = parsed.green;
83 let _ = self.cache.replace(Some((parsed.value, parsed.span_tree)));
84 self.last_repair_scope.set(Some(repair_scope));
85 Ok(())
86 }
87}