Skip to main content

yaml_schema/validation/
annotations.rs

1//! Annotation state for JSON Schema 2020-12 `unevaluatedProperties` / `unevaluatedItems`.
2
3use std::cell::RefCell;
4use std::collections::HashSet;
5use std::rc::Rc;
6
7/// Successfully evaluated object property names at one instance (for `unevaluatedProperties`).
8#[derive(Debug, Clone, Default)]
9pub struct ObjectEvaluatedNames {
10    pub names: Rc<RefCell<HashSet<String>>>,
11}
12
13impl ObjectEvaluatedNames {
14    pub fn new() -> Self {
15        Self::default()
16    }
17
18    pub fn insert(&self, name: impl Into<String>) {
19        self.names.borrow_mut().insert(name.into());
20    }
21
22    pub fn extend(&self, other: &HashSet<String>) {
23        self.names.borrow_mut().extend(other.iter().cloned());
24    }
25
26    pub fn snapshot(&self) -> HashSet<String> {
27        self.names.borrow().clone()
28    }
29}
30
31/// Tracks keywords that feed `unevaluatedItems` at one array instance (JSON Schema 2020-12 §11.2).
32#[derive(Debug, Clone, Default)]
33pub struct ArrayUnevaluatedAnnotations {
34    /// Any annotation from prefixItems, items, contains, unevaluatedItems, or merged in-place applicators.
35    pub saw_relevant: bool,
36    /// Boolean true from `items` or nested `unevaluatedItems` → ignore unevaluatedItems.
37    pub full_coverage: bool,
38    /// Largest index prefixItems applied a subschema to (`None` if prefixItems absent or empty).
39    pub prefix_largest: Option<usize>,
40    /// Indices where contains matched successfully.
41    pub contains_indices: HashSet<usize>,
42    /// Contains produced boolean true (every index).
43    pub contains_all: bool,
44}
45
46impl ArrayUnevaluatedAnnotations {
47    pub fn new_shared() -> Rc<RefCell<Self>> {
48        Rc::new(RefCell::new(Self::default()))
49    }
50
51    /// Indices that must still be validated by `unevaluatedItems` (JSON Schema 2020-12 §11.2).
52    pub fn indices_requiring_unevaluated(&self, len: usize) -> Vec<usize> {
53        if self.full_coverage {
54            return Vec::new();
55        }
56        if !self.saw_relevant {
57            return (0..len).collect();
58        }
59        let start = self
60            .prefix_largest
61            .map(|p| p.saturating_add(1))
62            .unwrap_or(0);
63        let mut out = Vec::new();
64        for i in start..len {
65            if self.contains_all || self.contains_indices.contains(&i) {
66                continue;
67            }
68            out.push(i);
69        }
70        out
71    }
72
73    /// Merge another branch's annotations (e.g. successful `anyOf` sibling). Union semantics.
74    pub fn merge_from(&mut self, other: &ArrayUnevaluatedAnnotations) {
75        if other.saw_relevant {
76            self.saw_relevant = true;
77        }
78        if other.full_coverage {
79            self.full_coverage = true;
80        }
81        self.prefix_largest = match (self.prefix_largest, other.prefix_largest) {
82            (Some(a), Some(b)) => Some(a.max(b)),
83            (Some(a), None) => Some(a),
84            (None, Some(b)) => Some(b),
85            (None, None) => None,
86        };
87        if other.contains_all {
88            self.contains_all = true;
89        }
90        self.contains_indices
91            .extend(other.contains_indices.iter().copied());
92    }
93}