Skip to main content

shape/
normalize.rs

1//! Spec by example to node: a literal is optional and its own default, a
2//! type token is required, an object or array carries its children.
3
4use crate::builders::fault_node;
5use crate::expr::{expr_apply, key_expr_node, split_key_expr};
6use crate::node::{Kind, Node, Token};
7use crate::spec::Spec;
8use crate::value::Value;
9use indexmap::IndexMap;
10use regex::Regex;
11use std::collections::HashMap;
12
13/// How a spec is read.
14#[derive(Clone, Debug)]
15pub struct Options {
16    /// Read object keys like `"x: Min(1)"` as key expressions. On by default.
17    pub key_expr: bool,
18    /// Read `"x$$"` keys as metadata for `"x"`. Off by default.
19    pub meta: bool,
20    /// The suffix of a metadata key.
21    pub meta_suffix: String,
22    /// Read the string under the mark key as an expression applied to the
23    /// object. Off by default.
24    pub val_expr: bool,
25    /// The mark key of a value expression; the meta suffix when empty.
26    pub val_expr_mark: String,
27}
28
29impl Default for Options {
30    fn default() -> Self {
31        Options {
32            key_expr: true,
33            meta: false,
34            meta_suffix: "$$".to_string(),
35            val_expr: false,
36            val_expr_mark: String::new(),
37        }
38    }
39}
40
41impl Options {
42    fn mark(&self) -> &str {
43        if self.val_expr_mark.is_empty() {
44            &self.meta_suffix
45        } else {
46            &self.val_expr_mark
47        }
48    }
49}
50
51/// Compile a spec into a node.
52pub fn normalize(spec: Spec) -> Node {
53    normalize_with(spec, &Options::default())
54}
55
56/// Compile a spec into a node, with options.
57pub fn normalize_with(spec: Spec, opts: &Options) -> Node {
58    match spec {
59        Spec::Node(n) => *n,
60        Spec::Token(t) => type_token_node(t.kind()),
61        Spec::Regex(re) => regexp_node(re),
62        Spec::Regexp(src) => regexp_node_src(&src),
63        Spec::Value(v) => literal_node_with(v, opts),
64        Spec::Arr(items) => normalize_array_with(items, opts),
65        Spec::Obj(pairs) => normalize_object_with(pairs, opts),
66    }
67}
68
69/// The node a literal stands for: optional, with the literal as its default.
70pub fn literal_node(v: Value) -> Node {
71    literal_node_with(v, &Options::default())
72}
73
74fn literal_node_with(v: Value, opts: &Options) -> Node {
75    match v {
76        // `undefined` as a spec is `Any`, as in TypeScript.
77        Value::Undefined => Node::of(Kind::Any),
78        Value::Null => Node {
79            has_default: true,
80            default: Value::Null,
81            literal: Some(Value::Null),
82            ..Node::of(Kind::Null)
83        },
84        Value::Str(s) => Node {
85            has_default: true,
86            empty: s.is_empty(),
87            default: Value::Str(s.clone()),
88            literal: Some(Value::Str(s)),
89            ..Node::of(Kind::String)
90        },
91        Value::Bool(b) => Node {
92            has_default: true,
93            default: Value::Bool(b),
94            literal: Some(Value::Bool(b)),
95            ..Node::of(Kind::Boolean)
96        },
97        Value::Num(n) if n.is_nan() => nan_node(),
98        Value::Num(n) => Node {
99            has_default: true,
100            default: Value::Num(n),
101            literal: Some(Value::Num(n)),
102            ..Node::of(Kind::Number)
103        },
104        Value::BigInt(b) => Node {
105            has_default: true,
106            default: Value::BigInt(b.clone()),
107            literal: Some(Value::BigInt(b)),
108            ..Node::of(Kind::BigInt)
109        },
110        Value::Date(ms) => Node {
111            has_default: true,
112            default: Value::Date(ms),
113            literal: Some(Value::Date(ms)),
114            ..Node::of(Kind::Date)
115        },
116        Value::Func(id) => Node {
117            has_default: true,
118            default: Value::Func(id),
119            literal: Some(Value::Func(id)),
120            ..Node::of(Kind::Function)
121        },
122        Value::Arr(items) => {
123            normalize_array_with(items.into_iter().map(Spec::Value).collect(), opts)
124        }
125        Value::Obj(m) => normalize_object_with(
126            m.into_iter().map(|(k, v)| (k, Spec::Value(v))).collect(),
127            opts,
128        ),
129    }
130}
131
132pub(crate) fn nan_node() -> Node {
133    Node {
134        has_default: true,
135        default: Value::Num(f64::NAN),
136        literal: Some(Value::Num(f64::NAN)),
137        ..Node::of(Kind::NaN)
138    }
139}
140
141/// A regexp is a required string that must match it. The pattern is held
142/// to the shared subset and compiled for this engine; one outside it makes
143/// a fault node, as a builder given a wrong argument does.
144pub(crate) fn regexp_node(re: Regex) -> Node {
145    regexp_node_src(re.as_str())
146}
147
148/// The regexp node of a pattern text.
149pub(crate) fn regexp_node_src(src: &str) -> Node {
150    match crate::regexp::compile_regexp(src) {
151        Ok(engine) => Node {
152            regexp: Some(engine),
153            regexp_src: src.to_string(),
154            required: true,
155            required_set: true,
156            ..Node::of(Kind::Regexp)
157        },
158        Err(msg) => crate::builders::fault_node(msg),
159    }
160}
161
162/// A type token: required (but for `Any`), carrying the kind's empty value as
163/// the default it injects once made optional.
164pub(crate) fn type_token_node(kind: Kind) -> Node {
165    let mut n = Node {
166        required: kind != Kind::Any,
167        required_set: true,
168        kind_set: true,
169        has_default: true,
170        default: Node::zero_for(kind),
171        ..Node::of(kind)
172    };
173    // The Object token is open, as it is in TypeScript. The Array token has
174    // no element shape, as it has none there either: an array with no shape
175    // for its elements accepts them all, which is what the token says.
176    if kind == Kind::Object {
177        n.obj_rest = Some(Box::new(Node::of(Kind::Any)));
178    }
179    n
180}
181
182pub(crate) fn normalize_array_with(items: Vec<Spec>, opts: &Options) -> Node {
183    let mut n = Node {
184        default: Value::Arr(Vec::new()),
185        ..Node::of(Kind::Array)
186    };
187    match items.len() {
188        0 => {}
189        1 => {
190            n.arr_child = Some(Box::new(normalize_with(
191                items.into_iter().next().unwrap(),
192                opts,
193            )))
194        }
195        _ => n.arr_children = items.into_iter().map(|s| normalize_with(s, opts)).collect(),
196    }
197    n
198}
199
200/// An object spec: its children in order, with key expressions, metadata
201/// sidecars and the value expression read as the options say.
202pub(crate) fn normalize_object_with(pairs: Vec<(String, Spec)>, opts: &Options) -> Node {
203    let mut n = Node {
204        default: Value::Obj(Default::default()),
205        obj_children: IndexMap::with_capacity(pairs.len()),
206        ..Node::of(Kind::Object)
207    };
208    if pairs.is_empty() {
209        n.obj_rest = Some(Box::new(Node::of(Kind::Any)));
210        return n;
211    }
212
213    let names: Vec<String> = pairs.iter().map(|(k, _)| k.clone()).collect();
214    let suffix = opts.meta_suffix.as_str();
215    // A key ending in the suffix is metadata for the key it names, when
216    // that key exists; otherwise it is a key like any other.
217    let meta_base = |k: &str| -> Option<String> {
218        if !opts.meta || k == suffix {
219            return None;
220        }
221        let base = k.strip_suffix(suffix)?;
222        names.iter().any(|n| n == base).then(|| base.to_string())
223    };
224    let mut pending: HashMap<String, IndexMap<String, Value>> = HashMap::new();
225    for (k, v) in &pairs {
226        if let Some(base) = meta_base(k) {
227            let mut meta = IndexMap::new();
228            match v {
229                Spec::Value(Value::Str(s)) => {
230                    meta.insert("short".to_string(), Value::Str(s.clone()));
231                }
232                Spec::Obj(entries) => {
233                    for (mk, mv) in entries {
234                        if let Spec::Value(mv) = mv {
235                            meta.insert(mk.clone(), mv.clone());
236                        }
237                    }
238                }
239                Spec::Value(other) => {
240                    meta.insert("value".to_string(), other.clone());
241                }
242                _ => {}
243            }
244            pending.insert(base, meta);
245        }
246    }
247
248    let mut val_expr: Option<String> = None;
249    for (k, v) in pairs {
250        if meta_base(&k).is_some() {
251            continue;
252        }
253        if opts.val_expr && k == opts.mark() {
254            if let Spec::Value(Value::Str(src)) = &v {
255                val_expr = Some(src.clone());
256                continue;
257            }
258        }
259        let (real_key, mut cn) = match split_key_expr(&k).filter(|_| opts.key_expr) {
260            Some((name, src)) => (name, key_expr_node(&src, v)),
261            None => (k, normalize_with(v, opts)),
262        };
263        if let Some(meta) = pending.remove(&real_key) {
264            cn.meta.extend(meta);
265        }
266        n.obj_children.insert(real_key, cn);
267    }
268
269    // The value expression applies to the object itself, in place: "Open"
270    // opens it rather than replacing it.
271    if let Some(src) = val_expr {
272        n = match expr_apply(&src, Spec::from(n)) {
273            Ok(applied) => applied,
274            Err(e) => fault_node(format!("Shape: value expression {:?}: {}", src, e)),
275        };
276    }
277    n
278}
279
280impl From<Token> for Node {
281    fn from(t: Token) -> Self {
282        type_token_node(t.kind())
283    }
284}
285
286#[cfg(test)]
287mod tests {
288    use super::*;
289    use crate::spec::{arr, obj};
290
291    #[test]
292    fn literals_are_optional_defaults() {
293        let n = normalize(Spec::from("x"));
294        assert!(n.kind == Kind::String && n.has_default && !n.required && !n.empty);
295        assert!(normalize(Spec::from("")).empty);
296        assert_eq!(normalize(Spec::from(true)).kind, Kind::Boolean);
297        assert_eq!(normalize(Spec::from(1.5)).kind, Kind::Number);
298        assert_eq!(normalize(Spec::from(f64::NAN)).kind, Kind::NaN);
299        assert_eq!(normalize(Spec::Value(Value::Null)).kind, Kind::Null);
300        assert_eq!(normalize(Spec::Value(Value::Undefined)).kind, Kind::Any);
301        assert_eq!(normalize(Spec::Value(Value::Date(1))).kind, Kind::Date);
302        assert_eq!(normalize(Spec::Value(Value::Func(1))).kind, Kind::Function);
303        assert_eq!(
304            normalize(Spec::from(num_bigint::BigInt::from(1))).kind,
305            Kind::BigInt
306        );
307        let n = normalize(Spec::Value(Value::Arr(vec![Value::from(1)])));
308        assert!(n.arr_child.is_some());
309        let mut m = crate::value::Map::new();
310        m.insert("a".into(), Value::from(1));
311        let n = normalize(Spec::Value(Value::Obj(m)));
312        assert_eq!(n.obj_children.len(), 1);
313    }
314
315    #[test]
316    fn tokens_are_required() {
317        let n = normalize(Spec::from(Token::String));
318        assert!(n.required && n.has_default && n.default == Value::Str(String::new()));
319        assert!(!normalize(Spec::from(Token::Any)).required);
320        let o = normalize(Spec::from(Token::Object));
321        assert!(o.is_open());
322        // The Array token has no element shape: an array that says nothing
323        // about its elements accepts them all.
324        let a = normalize(Spec::from(Token::Array));
325        assert!(a.arr_child.is_none() && a.arr_children.is_empty());
326        let n: Node = Token::Number.into();
327        assert_eq!(n.kind, Kind::Number);
328        let r = normalize(Spec::from(Regex::new("^a$").unwrap()));
329        assert!(r.kind == Kind::Regexp && r.required);
330        assert!(matches!(
331            normalize(Spec::from(Node::of(Kind::Never))).kind,
332            Kind::Never
333        ));
334    }
335
336    #[test]
337    fn containers() {
338        assert!(normalize(arr::<Spec>([])).arr_child.is_none());
339        assert!(normalize(arr([Token::String])).arr_child.is_some());
340        assert_eq!(
341            normalize(arr([Spec::from(Token::String), Spec::from(1)]))
342                .arr_children
343                .len(),
344            2
345        );
346        let o = normalize(obj([("b", 1), ("a", 2)]));
347        let keys: Vec<&String> = o.obj_children.keys().collect();
348        assert_eq!(keys, vec!["b", "a"]);
349        assert!(!o.is_open());
350        assert!(normalize(obj::<&str, Spec>([])).is_open());
351    }
352}
353
354#[cfg(test)]
355mod option_tests {
356    use super::*;
357    use crate::spec::obj;
358    use crate::Schema;
359
360    fn j(s: &str) -> Value {
361        Value::from(serde_json::from_str::<serde_json::Value>(s).unwrap())
362    }
363
364    #[test]
365    fn meta_sidecars() {
366        let opts = Options {
367            meta: true,
368            ..Default::default()
369        };
370        let s = Schema::with_options(
371            obj([
372                ("a", Spec::from(1)),
373                ("a$$", Spec::from("short text")),
374                ("b", Spec::from(2)),
375                (
376                    "b$$",
377                    obj([("k", Spec::from(1)), ("z", Spec::from(Token::Number))]),
378                ),
379                ("c", Spec::from(3)),
380                ("c$$", Spec::from(7)),
381                ("d", Spec::from(4)),
382                ("d$$", Spec::from(Token::String)),
383                ("e$$", Spec::from(5)),
384                ("$$", Spec::from(6)),
385            ]),
386            &opts,
387        );
388        let c = &s.node().obj_children;
389        assert_eq!(c["a"].meta["short"], Value::from("short text"));
390        assert_eq!(c["b"].meta["k"], Value::from(1));
391        assert!(!c["b"].meta.contains_key("z"));
392        assert_eq!(c["c"].meta["value"], Value::from(7));
393        assert!(c["d"].meta.is_empty());
394        assert!(!c.contains_key("a$$"));
395        assert_eq!(c["e$$"].default, Value::from(5));
396        assert_eq!(c["$$"].default, Value::from(6));
397        let custom = Options {
398            meta: true,
399            meta_suffix: "__".into(),
400            ..Default::default()
401        };
402        let s = Schema::with_options(
403            obj([("a", Spec::from(1)), ("a__", Spec::from("s"))]),
404            &custom,
405        );
406        assert_eq!(s.node().obj_children["a"].meta["short"], Value::from("s"));
407        // Off by default: a sidecar is a key like any other.
408        let s = Schema::new(obj([("a", Spec::from(1)), ("a$$", Spec::from("s"))]));
409        assert!(s.node().obj_children.contains_key("a$$"));
410    }
411
412    #[test]
413    fn value_expressions_apply_to_the_object() {
414        let opts = Options {
415            val_expr: true,
416            ..Default::default()
417        };
418        let s = Schema::with_options(
419            obj([("a", Spec::from(1)), ("$$", Spec::from("Open"))]),
420            &opts,
421        );
422        assert!(s.node().is_open());
423        assert_eq!(
424            serde_json::Value::from(s.validate(j(r#"{"a":2,"z":1}"#)).unwrap()),
425            serde_json::json!({"a":2,"z":1})
426        );
427        let s = Schema::with_options(
428            obj([("a", Spec::from(1)), ("$$", Spec::from("Min(2)"))]),
429            &opts,
430        );
431        assert!(s.validate(j(r#"{"a":2}"#)).is_err());
432        let s = Schema::with_options(obj([("a", Spec::from(1)), ("$$", Spec::from(5))]), &opts);
433        assert!(s.node().obj_children.contains_key("$$"));
434        let s = Schema::with_options(
435            obj([("a", Spec::from(1)), ("$$", Spec::from("bogus"))]),
436            &opts,
437        );
438        assert_eq!(
439            s.validate(j(r#"{"a":2}"#)).unwrap_err().to_string(),
440            "Shape: value expression \"bogus\": Shape: unexpected token bogus in builder expression bogus"
441        );
442        let marked = Options {
443            val_expr: true,
444            val_expr_mark: "@".into(),
445            ..Default::default()
446        };
447        let s = Schema::with_options(
448            obj([("a", Spec::from(1)), ("@", Spec::from("Open"))]),
449            &marked,
450        );
451        assert!(s.node().is_open());
452        // Off by default.
453        let s = Schema::new(obj([("a", Spec::from(1)), ("$$", Spec::from("Open"))]));
454        assert!(!s.node().is_open());
455    }
456
457    #[test]
458    fn key_expressions_can_be_turned_off() {
459        let s = Schema::new(obj([("a: Min(1)", Spec::from(0))]));
460        assert!(s.node().obj_children.contains_key("a"));
461        let opts = Options {
462            key_expr: false,
463            ..Default::default()
464        };
465        let s = Schema::with_options(obj([("a: Min(1)", Spec::from(0))]), &opts);
466        assert!(s.node().obj_children.contains_key("a: Min(1)"));
467    }
468}