Skip to main content

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}