Skip to main content

shape/
argu.rs

1//! Argu: positional arguments matched against an ordered spec, as a call
2//! signature is: `argu.validate(args, "foo", spec)` gives the arguments by
3//! name. A `skip` slot is optional and shifts the ones after it; a `rest`
4//! slot captures whatever remains.
5
6use crate::context::Context;
7use crate::error::ValidationError;
8use crate::node::{Kind, Node};
9use crate::normalize::normalize;
10use crate::spec::Spec;
11use crate::validate::{validate_node, Cur, Walk};
12use crate::value::{Map, Value};
13use std::fmt;
14
15/// An argument list that does not match.
16#[derive(Clone, Debug, PartialEq, Eq)]
17pub struct ArguError(pub String);
18
19impl fmt::Display for ArguError {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        f.write_str(&self.0)
22    }
23}
24
25impl std::error::Error for ArguError {}
26
27/// A positional-argument validator with a namespace name.
28#[derive(Clone, Debug)]
29pub struct Argu {
30    name: String,
31}
32
33/// A compiled signature: the slot names and their shapes, in order.
34#[derive(Clone, Debug)]
35pub struct Signature {
36    prefix: String,
37    keys: Vec<String>,
38    nodes: Vec<Node>,
39}
40
41impl Argu {
42    pub fn new(name: impl Into<String>) -> Argu {
43        Argu { name: name.into() }
44    }
45
46    /// Match the arguments against the spec, naming the call `whence`.
47    pub fn validate<I, K, S>(
48        &self,
49        args: Vec<Value>,
50        whence: &str,
51        spec: I,
52    ) -> Result<Map, ArguError>
53    where
54        I: IntoIterator<Item = (K, S)>,
55        K: Into<String>,
56        S: Into<Spec>,
57    {
58        self.signature(whence, spec)?.apply(args)
59    }
60
61    /// Compile the spec once, for many calls.
62    pub fn signature<I, K, S>(&self, whence: &str, spec: I) -> Result<Signature, ArguError>
63    where
64        I: IntoIterator<Item = (K, S)>,
65        K: Into<String>,
66        S: Into<Spec>,
67    {
68        let mut keys = Vec::new();
69        let mut nodes = Vec::new();
70        for (k, s) in spec {
71            keys.push(k.into());
72            let mut n = normalize(s.into());
73            crate::prepare_node(&mut n);
74            nodes.push(n);
75        }
76        if keys.is_empty() {
77            return Err(ArguError(format!("{}: empty argument spec", self.name)));
78        }
79        let prefix = if whence.is_empty() {
80            self.name.clone()
81        } else {
82            format!("{} ({})", self.name, whence)
83        };
84        Ok(Signature {
85            prefix,
86            keys,
87            nodes,
88        })
89    }
90}
91
92fn is_rest(n: &Node) -> bool {
93    n.kind == Kind::Array && n.arr_rest.is_some()
94}
95
96fn run(n: &Node, val: Value, key: &str, is_match: bool) -> (Value, ValidationError) {
97    let mut ctx = Context::new();
98    ctx.terse = is_match;
99    let mut verr = ValidationError {
100        terse: is_match,
101        ..Default::default()
102    };
103    // An argument list is compiled from its own nodes: no definitions.
104    let defs = std::collections::HashMap::new();
105    let mut w = Walk {
106        ctx: &mut ctx,
107        defs: &defs,
108        is_match,
109        path: if key.is_empty() {
110            Vec::new()
111        } else {
112            vec![crate::context::PathPart::Key(key.into())]
113        },
114        paths: true,
115    };
116    let mut val = val;
117    let kept = validate_node(n, Cur::Mut(&mut val), key, false, &mut w, &mut verr);
118    if !kept {
119        val = Value::Undefined;
120    }
121    (val, verr)
122}
123
124impl Signature {
125    /// Match the arguments.
126    pub fn apply(&self, args: Vec<Value>) -> Result<Map, ArguError> {
127        let mut out = Map::new();
128        let mut idx = 0usize;
129        let require = |n: &Node, v: Value, key: &str| -> Result<Value, ArguError> {
130            let (val, verr) = run(n, v, key, false);
131            if verr.has_any() {
132                return Err(ArguError(format!("{}: {}", self.prefix, verr)));
133            }
134            Ok(val)
135        };
136        for (key, n) in self.keys.iter().zip(self.nodes.iter()) {
137            if is_rest(n) {
138                // The arguments that remain, which is none at all when the
139                // rest comes last and the caller stopped: the canonical Argu
140                // leaves the slot an empty list.
141                let rem: Vec<Value> = args[idx.min(args.len())..].to_vec();
142                let child = n.arr_rest.as_deref().unwrap();
143                let mut validated = Vec::with_capacity(rem.len());
144                for v in rem {
145                    validated.push(require(child, v, key)?);
146                }
147                out.insert(key.clone(), Value::Arr(validated));
148                idx = args.len();
149            } else if n.skippable {
150                if idx >= args.len() {
151                    out.insert(key.clone(), Value::Undefined);
152                    continue;
153                }
154                // The slot is taken when the argument matches, and skipped
155                // otherwise.
156                let mut tester = n.clone();
157                tester.skippable = false;
158                tester.silent = false;
159                let (_, probe) = run(&tester, args[idx].clone(), "", true);
160                if !probe.has_any() {
161                    out.insert(key.clone(), require(&tester, args[idx].clone(), key)?);
162                    idx += 1;
163                } else {
164                    out.insert(key.clone(), Value::Undefined);
165                }
166            } else {
167                let v = args.get(idx).cloned().unwrap_or(Value::Undefined);
168                out.insert(key.clone(), require(n, v, key)?);
169                if idx < args.len() {
170                    idx += 1;
171                }
172            }
173        }
174        if idx < args.len() {
175            let expected = self.nodes.iter().filter(|n| !is_rest(n)).count();
176            return Err(ArguError(format!(
177                "{}: Too many arguments for type signature (was {}, expected {})",
178                self.prefix,
179                args.len(),
180                expected
181            )));
182        }
183        Ok(out)
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190    use crate::builders::{rest, skip};
191    use crate::node::Token;
192
193    #[test]
194    fn matches_positional_arguments() {
195        let argu = Argu::new("mylib");
196        let sig = argu
197            .signature(
198                "foo",
199                [
200                    ("a", Spec::from(Token::Number)),
201                    ("b", Spec::from(Token::String)),
202                ],
203            )
204            .unwrap();
205        let out = sig.apply(vec![Value::from(2), Value::from("x")]).unwrap();
206        assert_eq!(
207            serde_json::Value::from(Value::Obj(out)),
208            serde_json::json!({"a": 2, "b": "x"})
209        );
210        assert_eq!(
211            sig.apply(vec![Value::from(2)]).unwrap_err().0,
212            "mylib (foo): Validation failed for property \"b\" because the property is missing."
213        );
214        assert_eq!(
215            sig.apply(vec![Value::from("z"), Value::from("x")]).unwrap_err().0,
216            "mylib (foo): Validation failed for property \"a\" with string \"z\" because the string is not of type number."
217        );
218        assert_eq!(
219            sig.apply(vec![Value::from(1), Value::from("x"), Value::from(3)])
220                .unwrap_err()
221                .0,
222            "mylib (foo): Too many arguments for type signature (was 3, expected 2)"
223        );
224        // A skip slot shifts.
225        let out = argu
226            .validate(
227                vec![Value::from("x")],
228                "",
229                [
230                    ("a", Spec::from(skip(Token::Number))),
231                    ("b", Spec::from(Token::String)),
232                ],
233            )
234            .unwrap();
235        assert_eq!(
236            serde_json::Value::from(Value::Obj(out)),
237            serde_json::json!({"b": "x"})
238        );
239        let out = argu
240            .validate(
241                vec![Value::from(1), Value::from("x")],
242                "",
243                [
244                    ("a", Spec::from(skip(Token::Number))),
245                    ("b", Spec::from(Token::String)),
246                ],
247            )
248            .unwrap();
249        assert_eq!(
250            serde_json::Value::from(Value::Obj(out)),
251            serde_json::json!({"a": 1, "b": "x"})
252        );
253        let out = argu
254            .validate(vec![], "", [("a", Spec::from(skip(Token::Number)))])
255            .unwrap();
256        assert_eq!(
257            serde_json::Value::from(Value::Obj(out)),
258            serde_json::json!({})
259        );
260        // A rest slot captures the tail; none at all is one undefined.
261        let out = argu
262            .validate(
263                vec![Value::from(1), Value::from(2), Value::from(3)],
264                "",
265                [
266                    ("a", Spec::from(Token::Number)),
267                    (
268                        "d",
269                        Spec::from(rest(Token::Number, crate::spec::arr::<Spec>([]))),
270                    ),
271                ],
272            )
273            .unwrap();
274        assert_eq!(
275            serde_json::Value::from(Value::Obj(out)),
276            serde_json::json!({"a": 1, "d": [2, 3]})
277        );
278        let out = argu
279            .validate(
280                vec![Value::from(1)],
281                "",
282                [
283                    ("a", Spec::from(Token::Number)),
284                    (
285                        "d",
286                        Spec::from(rest(Token::Any, crate::spec::arr::<Spec>([]))),
287                    ),
288                ],
289            )
290            .unwrap();
291        assert_eq!(
292            serde_json::Value::from(Value::Obj(out)),
293            serde_json::json!({"a": 1, "d": []})
294        );
295        assert!(argu
296            .validate(
297                vec![Value::from(1), Value::from("x")],
298                "",
299                [
300                    ("a", Spec::from(Token::Number)),
301                    (
302                        "d",
303                        Spec::from(rest(Token::Number, crate::spec::arr::<Spec>([])))
304                    )
305                ],
306            )
307            .is_err());
308        assert_eq!(
309            argu.signature("f", Vec::<(String, Spec)>::new())
310                .unwrap_err()
311                .0,
312            "mylib: empty argument spec"
313        );
314        assert_eq!(format!("{}", ArguError("e".into())), "e");
315    }
316}