Skip to main content

yaml_schema/validation/
context.rs

1use std::cell::RefCell;
2use std::collections::HashSet;
3use std::rc::Rc;
4
5use crate::RootSchema;
6use crate::YamlSchema;
7use crate::validation::ValidationError;
8
9/// The validation context
10#[derive(Debug, Default)]
11pub struct Context<'r> {
12    /// We use an Option here so tests can be run without a root schema
13    pub root_schema: Option<&'r RootSchema<'r>>,
14    pub current_schema: Option<&'r YamlSchema<'r>>,
15    pub current_path: Vec<String>,
16    pub stream_started: bool,
17    pub stream_ended: bool,
18    pub errors: Rc<RefCell<Vec<ValidationError>>>,
19    pub fail_fast: bool,
20    /// Tracks `($ref, value_position)` pairs currently being resolved to detect circular references.
21    /// The value position is the byte offset of the YAML value's span start, so the same ref
22    /// applied to a nested value is allowed (legitimate recursion) while the same ref
23    /// on the same value is detected as a cycle.
24    pub resolving_refs: Rc<RefCell<HashSet<(String, usize)>>>,
25}
26
27impl<'r> Context<'r> {
28    /// Returns true if there are any errors in the context
29    pub fn has_errors(&self) -> bool {
30        !self.errors.borrow().is_empty()
31    }
32
33    /// Returns the current path as a string separated by "."
34    pub fn path(&self) -> String {
35        self.current_path.join(".")
36    }
37
38    pub fn new(fail_fast: bool) -> Context<'r> {
39        Context {
40            fail_fast,
41            ..Default::default()
42        }
43    }
44
45    pub fn get_sub_context(&self) -> Context<'r> {
46        Context {
47            root_schema: self.root_schema,
48            current_schema: self.current_schema,
49            current_path: self.current_path.clone(),
50            stream_started: self.stream_started,
51            stream_ended: self.stream_ended,
52            errors: Rc::new(RefCell::new(Vec::new())),
53            fail_fast: self.fail_fast,
54            resolving_refs: self.resolving_refs.clone(),
55        }
56    }
57
58    pub fn with_root_schema(root_schema: &'r RootSchema, fail_fast: bool) -> Context<'r> {
59        Context {
60            root_schema: Some(root_schema),
61            fail_fast,
62            ..Default::default()
63        }
64    }
65
66    fn push_error(&self, error: ValidationError) {
67        self.errors.borrow_mut().push(error);
68    }
69
70    pub fn add_doc_error<V: Into<String>>(&self, error: V) {
71        let path = self.path();
72        self.push_error(ValidationError {
73            path,
74            marker: None,
75            error: error.into(),
76        });
77    }
78
79    /// Adds an error message to the current context, with the current path and with location marker
80    pub fn add_error<V: Into<String>>(&self, marked_yaml: &saphyr::MarkedYaml, error: V) {
81        let path = self.path();
82        self.push_error(ValidationError {
83            path,
84            marker: Some(marked_yaml.span.start),
85            error: error.into(),
86        });
87    }
88
89    /// Appends all the errors to the current context
90    pub fn extend_errors(&self, errors: Vec<ValidationError>) {
91        self.errors.borrow_mut().extend(errors);
92    }
93
94    /// Append a path to the current path
95    pub fn append_path<V: Into<String>>(&self, path: V) -> Context<'r> {
96        let mut new_path = self.current_path.clone();
97        new_path.push(path.into());
98        Context {
99            root_schema: self.root_schema,
100            current_schema: self.current_schema,
101            current_path: new_path,
102            errors: self.errors.clone(),
103            fail_fast: self.fail_fast,
104            stream_ended: self.stream_ended,
105            stream_started: self.stream_started,
106            resolving_refs: self.resolving_refs.clone(),
107        }
108    }
109
110    /// Returns `true` if the given ref is already being resolved for the given
111    /// YAML value (identified by its span start index), indicating a cycle.
112    pub fn is_resolving_ref(&self, ref_name: &str, value: &saphyr::MarkedYaml) -> bool {
113        let key = (ref_name.to_string(), value.span.start.index());
114        self.resolving_refs.borrow().contains(&key)
115    }
116
117    /// Mark a `(ref, value_position)` pair as currently being resolved.
118    pub fn begin_resolving_ref(&self, ref_name: &str, value: &saphyr::MarkedYaml) {
119        let key = (ref_name.to_string(), value.span.start.index());
120        self.resolving_refs.borrow_mut().insert(key);
121    }
122
123    /// Remove a `(ref, value_position)` pair from the resolving set after resolution completes.
124    pub fn end_resolving_ref(&self, ref_name: &str, value: &saphyr::MarkedYaml) {
125        let key = (ref_name.to_string(), value.span.start.index());
126        self.resolving_refs.borrow_mut().remove(&key);
127    }
128}