Skip to main content

shape/
context.rs

1//! What flows through a validation: the caller's context, the state a
2//! validator sees at its node, and the update it fills in.
3
4use crate::error::FieldError;
5use crate::node::Node;
6use crate::value::Value;
7use std::any::Any;
8use std::collections::HashMap;
9use std::sync::Arc;
10
11/// A path element: an object key or an array index.
12#[derive(Clone, Debug, PartialEq)]
13pub enum PathPart {
14    /// A property key, shared with the node that declares it.
15    Key(Arc<str>),
16    Index(usize),
17}
18
19impl PathPart {
20    pub fn as_key(&self) -> String {
21        match self {
22            PathPart::Key(k) => k.to_string(),
23            PathPart::Index(i) => i.to_string(),
24        }
25    }
26
27    fn write_to(&self, out: &mut String) {
28        match self {
29            PathPart::Key(k) => out.push_str(k),
30            PathPart::Index(i) => out.push_str(&i.to_string()),
31        }
32    }
33}
34
35/// The caller's context: per-call state custom validators may share, and
36/// the definitions `Define` records for `Refer`.
37#[derive(Default)]
38pub struct Context {
39    /// Anything a validator wants to keep across nodes: a counter, a handle,
40    /// a domain object. Type-erased, as TypeScript takes any property and Go
41    /// a `map[string]any`.
42    pub custom: HashMap<String, Box<dyn Any + Send + Sync>>,
43    /// The nodes `Define` recorded during this call.
44    pub refs: HashMap<String, Arc<Node>>,
45    /// The errors of the call, when the caller asked for them.
46    pub err: Vec<FieldError>,
47    /// The caller wants a verdict only, so an error is recorded without its
48    /// path, text or value rendering.
49    pub(crate) terse: bool,
50}
51
52impl Context {
53    pub fn new() -> Context {
54        Context::default()
55    }
56
57    /// A typed read of `custom`.
58    pub fn get<T: 'static>(&self, key: &str) -> Option<&T> {
59        self.custom.get(key).and_then(|b| b.downcast_ref::<T>())
60    }
61
62    /// A typed write to `custom`.
63    pub fn set<T: Any + Send + Sync>(&mut self, key: &str, value: T) {
64        self.custom.insert(key.to_string(), Box::new(value));
65    }
66}
67
68/// What a custom validator sees: where it is, what it is looking at.
69pub struct State<'a> {
70    /// The path from the root, the current key last: array indices as
71    /// indices, keys as keys.
72    pub path_arr: &'a [PathPart],
73    /// The immediate key or index.
74    pub key: &'a str,
75    /// The value under validation.
76    pub value: &'a Value,
77    /// The node of the value.
78    pub node: &'a Node,
79    /// Whether the parent is an array (an index) or an object (a property).
80    pub parent_is_array: bool,
81    /// True on a `matches` or `valid` call: nothing is produced.
82    pub is_match: bool,
83    pub ctx: &'a mut Context,
84    /// The value is missing, rather than present and null.
85    pub absent: bool,
86    /// The name of the validator running, for `check "<name>" failed`.
87    pub check_name: &'a str,
88    /// The schema's own definitions, as of its compile. Borrowed from the
89    /// schema for the walk: a context of its own cost an allocation and
90    /// an atomic per call.
91    pub(crate) defs: &'a HashMap<String, Arc<Node>>,
92}
93
94impl<'a> State<'a> {
95    /// The path as dotted text.
96    pub fn path_str(&self) -> String {
97        join_path(self.path_arr)
98    }
99
100    /// The path as keys, every index as its digits.
101    pub fn path_keys(&self) -> Vec<String> {
102        self.path_arr.iter().map(|p| p.as_key()).collect()
103    }
104}
105
106/// The dotted form of a path, empty segments skipped.
107pub fn join_path(path: &[PathPart]) -> String {
108    let mut out = String::new();
109    for p in path {
110        if matches!(p, PathPart::Key(k) if k.is_empty()) {
111            continue;
112        }
113        if !out.is_empty() {
114            out.push('.');
115        }
116        p.write_to(&mut out);
117    }
118    out
119}
120
121/// An error a validator reports.
122#[derive(Clone, Debug)]
123pub enum UpdateErr {
124    /// A message; `$PATH` and `$VALUE` are expanded.
125    Text(String),
126    /// A ready error, its path filled in when empty.
127    Field(Box<FieldError>),
128    /// Several ready errors.
129    Fields(Vec<FieldError>),
130}
131
132/// The bag a custom validator fills in.
133#[derive(Default)]
134pub struct Update {
135    /// Stop running further checks and the structural check.
136    pub done: bool,
137    /// The why code on failure.
138    pub why: Option<String>,
139    /// The numeric mark on failure.
140    pub mark: i64,
141    pub err: Option<UpdateErr>,
142    /// A replacement value.
143    pub val: Option<Value>,
144    /// A replacement node (used by `Refer`).
145    pub node: Option<Arc<Node>>,
146    /// The failure ends the whole walk's errors here.
147    pub fatal: bool,
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153
154    #[test]
155    fn context_custom_is_typed() {
156        let mut c = Context::new();
157        c.set("n", 3u32);
158        assert_eq!(c.get::<u32>("n"), Some(&3));
159        assert_eq!(c.get::<String>("n"), None);
160        assert_eq!(c.get::<u32>("m"), None);
161    }
162
163    #[test]
164    fn paths_join_and_render() {
165        let k = |s: &str| PathPart::Key(s.into());
166        assert_eq!(
167            join_path(&[k("a"), k(""), k("b"), PathPart::Index(1)]),
168            "a.b.1"
169        );
170        assert_eq!(join_path(&[]), "");
171        assert_eq!(PathPart::Key("k".into()).as_key(), "k");
172        assert_eq!(PathPart::Index(2).as_key(), "2");
173        let mut ctx = Context::new();
174        let node = Node::default();
175        let path = [k("a")];
176        let defs = HashMap::new();
177        let s = State {
178            path_arr: &path,
179            key: "a",
180            value: &Value::Null,
181            node: &node,
182            parent_is_array: false,
183            is_match: false,
184            ctx: &mut ctx,
185            absent: false,
186            check_name: "",
187            defs: &defs,
188        };
189        assert_eq!(s.path_str(), "a");
190        assert_eq!(s.path_keys(), vec!["a".to_string()]);
191        let u = Update::default();
192        assert!(!u.done && u.val.is_none());
193    }
194}