Skip to main content

shape/
algebra.rs

1//! Object algebra: `pick`, `omit`, `partial` and `extend` build a new object
2//! shape out of an existing one. The result is a fresh node, so the source
3//! is left as it was and one base can be reshaped many times. Key
4//! expressions in a source (`{"a: Min(2)": 0}`) are compiled on the way in,
5//! since the algebra has to know the real property names.
6
7use crate::builders::{buildize, fault_node};
8use crate::node::{Kind, Node};
9use crate::normalize::normalize;
10use crate::spec::Spec;
11use crate::value::Value;
12use indexmap::IndexMap;
13
14/// The property names an algebra builder is given: one, or a list.
15#[derive(Clone, Debug, Default, PartialEq, Eq)]
16pub struct Names(pub Vec<String>);
17
18impl From<&str> for Names {
19    fn from(s: &str) -> Self {
20        Names(vec![s.to_string()])
21    }
22}
23impl From<String> for Names {
24    fn from(s: String) -> Self {
25        Names(vec![s])
26    }
27}
28impl From<Vec<String>> for Names {
29    fn from(v: Vec<String>) -> Self {
30        Names(v)
31    }
32}
33impl From<Vec<&str>> for Names {
34    fn from(v: Vec<&str>) -> Self {
35        Names(v.into_iter().map(String::from).collect())
36    }
37}
38impl<const N: usize> From<[&str; N]> for Names {
39    fn from(v: [&str; N]) -> Self {
40        Names(v.iter().map(|s| s.to_string()).collect())
41    }
42}
43
44impl Names {
45    /// The names as a value carries them: one string, or a list of strings.
46    pub fn from_value(v: &Value, builder: &str) -> Result<Names, String> {
47        match v {
48            Value::Str(s) => Ok(Names(vec![s.clone()])),
49            Value::Arr(items) => {
50                let mut out = Vec::with_capacity(items.len());
51                for item in items {
52                    match item {
53                        Value::Str(s) => out.push(s.clone()),
54                        _ => return Err(format!("{} needs a list of property names", builder)),
55                    }
56                }
57                Ok(Names(out))
58            }
59            _ => Err(format!("{} needs a list of property names", builder)),
60        }
61    }
62}
63
64/// The shape an algebra builder works on, which has to be an object.
65fn object_base(spec: Spec, builder: &str) -> Result<Node, String> {
66    let n = buildize(spec);
67    if n.kind != Kind::Object {
68        return Err(format!("{} needs an object shape", builder));
69    }
70    Ok(n)
71}
72
73/// The base's settings with just these properties, in this order. An object
74/// default is narrowed to them too.
75fn object_node(mut base: Node, children: IndexMap<String, Node>) -> Node {
76    if let Value::Obj(dm) = &base.default {
77        let mut nd = crate::value::Map::with_capacity(children.len());
78        for k in children.keys() {
79            if let Some(dv) = dm.get(k) {
80                nd.insert(k.clone(), dv.clone());
81            }
82        }
83        base.default = Value::Obj(nd);
84    }
85    base.obj_children = children;
86    base.consumed.clear();
87    base
88}
89
90pub(crate) fn pick_node(names: Names, spec: Spec) -> Result<Node, String> {
91    let base = object_base(spec, "Pick")?;
92    for k in &names.0 {
93        if !base.obj_children.contains_key(k) {
94            return Err(format!("Pick: unknown property \"{}\"", k));
95        }
96    }
97    let children: IndexMap<String, Node> = base
98        .obj_children
99        .iter()
100        .filter(|(k, _)| names.0.contains(k))
101        .map(|(k, v)| (k.clone(), v.clone()))
102        .collect();
103    Ok(object_node(base, children))
104}
105
106pub(crate) fn omit_node(names: Names, spec: Spec) -> Result<Node, String> {
107    let base = object_base(spec, "Omit")?;
108    let children: IndexMap<String, Node> = base
109        .obj_children
110        .iter()
111        .filter(|(k, _)| !names.0.contains(k))
112        .map(|(k, v)| (k.clone(), v.clone()))
113        .collect();
114    Ok(object_node(base, children))
115}
116
117pub(crate) fn partial_node(spec: Spec) -> Result<Node, String> {
118    let base = object_base(spec, "Partial")?;
119    let children: IndexMap<String, Node> = base
120        .obj_children
121        .iter()
122        .map(|(k, v)| {
123            let mut cc = v.clone();
124            cc.required = false;
125            cc.required_set = true;
126            (k.clone(), cc)
127        })
128        .collect();
129    Ok(object_node(base, children))
130}
131
132pub(crate) fn extend_node(extra: Spec, spec: Spec) -> Result<Node, String> {
133    let base = object_base(spec, "Extend")?;
134    let ext = normalize(extra);
135    if ext.kind != Kind::Object {
136        return Err("Extend needs an object to extend with".to_string());
137    }
138    let mut children = base.obj_children.clone();
139    for (k, cn) in ext.obj_children {
140        children.insert(k, cn);
141    }
142    Ok(object_node(base, children))
143}
144
145fn or_fault(r: Result<Node, String>) -> Node {
146    r.unwrap_or_else(fault_node)
147}
148
149/// Keep only the named properties of an object shape. Naming one the shape
150/// does not declare is a fault: there is nothing there to pick.
151pub fn pick(names: impl Into<Names>, spec: impl Into<Spec>) -> Node {
152    or_fault(pick_node(names.into(), spec.into()))
153}
154
155/// Drop the named properties of an object shape. A name the shape does not
156/// declare is simply not there to drop.
157pub fn omit(names: impl Into<Names>, spec: impl Into<Spec>) -> Node {
158    or_fault(omit_node(names.into(), spec.into()))
159}
160
161/// Make every declared property of an object shape optional, as `optional`
162/// would: a type token then injects its empty value, a literal its own.
163/// Shallow: a nested object's own properties are as they were.
164pub fn partial(spec: impl Into<Spec>) -> Node {
165    or_fault(partial_node(spec.into()))
166}
167
168/// Add the properties of another object shape; a property both declare
169/// takes the extension's. The result stays open or closed as the base was.
170pub fn extend(extra: impl Into<Spec>, spec: impl Into<Spec>) -> Node {
171    or_fault(extend_node(extra.into(), spec.into()))
172}
173
174impl Node {
175    pub fn pick(self, names: impl Into<Names>) -> Node {
176        pick(names, self)
177    }
178    pub fn omit(self, names: impl Into<Names>) -> Node {
179        omit(names, self)
180    }
181    pub fn partial(self) -> Node {
182        partial(self)
183    }
184    pub fn extend(self, extra: impl Into<Spec>) -> Node {
185        extend(extra, self)
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192    use crate::builders::{any, open};
193    use crate::node::Token;
194    use crate::spec::obj;
195    use crate::Schema;
196
197    fn j(s: &str) -> Value {
198        Value::from(serde_json::from_str::<serde_json::Value>(s).unwrap())
199    }
200
201    fn run(s: &Schema, input: &str) -> String {
202        match s.validate(j(input)) {
203            Ok(v) => serde_json::Value::from(v).to_string(),
204            Err(e) => format!("ERR {}", e),
205        }
206    }
207
208    fn base() -> Spec {
209        obj([
210            ("a", Spec::from(1)),
211            ("b", Spec::from(Token::String)),
212            ("c", Spec::from(true)),
213        ])
214    }
215
216    #[test]
217    fn reshapes_objects() {
218        assert_eq!(run(&Schema::new(pick(["a"], base())), "{}"), r#"{"a":1}"#);
219        assert_eq!(run(&Schema::new(pick("a", base())), "{}"), r#"{"a":1}"#);
220        assert_eq!(
221            run(&Schema::new(pick(vec!["a", "c"], base())), r#"{"c":false}"#),
222            r#"{"c":false,"a":1}"#
223        );
224        assert_eq!(
225            run(&Schema::new(pick(vec!["b".to_string()], base())), "{}"),
226            "ERR Validation failed for property \"b\" because the property is missing."
227        );
228        assert_eq!(
229            run(&Schema::new(pick("z", base())), "{}"),
230            "ERR Pick: unknown property \"z\""
231        );
232        assert_eq!(
233            run(&Schema::new(pick("a", Token::Number)), "1"),
234            "ERR Pick needs an object shape"
235        );
236        assert_eq!(
237            run(&Schema::new(pick("a", open(base()))), r#"{"a":2,"z":1}"#),
238            r#"{"a":2,"z":1}"#
239        );
240        assert_eq!(
241            run(&Schema::new(omit(["b"], base())), "{}"),
242            r#"{"a":1,"c":true}"#
243        );
244        assert_eq!(
245            run(&Schema::new(omit(["z"], base())), r#"{"b":"x"}"#),
246            r#"{"b":"x","a":1,"c":true}"#
247        );
248        assert_eq!(
249            run(&Schema::new(omit(["b"], base())), r#"{"b":"x"}"#),
250            "ERR Validation failed for object \"{b:x}\" because the property \"b\" is not allowed."
251        );
252        assert_eq!(
253            run(&Schema::new(partial(base())), "{}"),
254            r#"{"a":1,"b":"","c":true}"#
255        );
256        assert_eq!(
257            run(&Schema::new(partial(any())), "{}"),
258            "ERR Partial needs an object shape"
259        );
260        assert_eq!(
261            run(
262                &Schema::new(extend(obj([("d", Spec::from(2))]), base())),
263                r#"{"b":"x"}"#
264            ),
265            r#"{"b":"x","a":1,"c":true,"d":2}"#
266        );
267        assert_eq!(
268            run(
269                &Schema::new(extend(obj([("a", Spec::from(Token::Number))]), base())),
270                r#"{"b":"x"}"#
271            ),
272            "ERR Validation failed for property \"a\" because the property is missing."
273        );
274        assert_eq!(
275            run(&Schema::new(extend(Token::Number, base())), "{}"),
276            "ERR Extend needs an object to extend with"
277        );
278        // The source is left as it was, and the chain forms reshape too.
279        let b = buildize(base());
280        let picked = b.clone().pick("a");
281        assert_eq!(b.obj_children.len(), 3);
282        assert_eq!(picked.obj_children.len(), 1);
283        assert_eq!(
284            b.clone().omit(Names(vec!["a".into()])).obj_children.len(),
285            2
286        );
287        assert!(!b.clone().partial().obj_children["b"].required);
288        assert_eq!(b.extend(obj([("z", Spec::from(1))])).obj_children.len(), 4);
289        // A default narrows with the shape.
290        let mut d = buildize(base());
291        d.has_default = true;
292        d.default = j(r#"{"a":5,"b":"q","c":false}"#);
293        let p = pick("a", d);
294        assert_eq!(p.default, j(r#"{"a":5}"#));
295        let mut bare = Node::of(Kind::Object);
296        bare.obj_children.insert("a".into(), buildize(1));
297        assert!(pick("a", bare).default.is_undefined());
298        assert_eq!(
299            Names::from_value(&j(r#"["a","b"]"#), "Pick").unwrap(),
300            Names::from(["a", "b"])
301        );
302        assert_eq!(
303            Names::from_value(&j("\"a\""), "Pick").unwrap(),
304            Names::from("a")
305        );
306        assert_eq!(Names::from("a".to_string()), Names::from("a"));
307        assert_eq!(Names::from(vec!["a".to_string()]), Names::from("a"));
308        assert_eq!(
309            Names::from_value(&j("[1]"), "Pick").unwrap_err(),
310            "Pick needs a list of property names"
311        );
312        assert_eq!(
313            Names::from_value(&j("1"), "Omit").unwrap_err(),
314            "Omit needs a list of property names"
315        );
316    }
317}