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    let mut w = Walk {
104        ctx: &mut ctx,
105        is_match,
106        path: if key.is_empty() {
107            Vec::new()
108        } else {
109            vec![crate::context::PathPart::Key(key.into())]
110        },
111        paths: true,
112    };
113    let mut val = val;
114    let kept = validate_node(n, Cur::Mut(&mut val), key, false, &mut w, &mut verr);
115    if !kept {
116        val = Value::Undefined;
117    }
118    (val, verr)
119}
120
121impl Signature {
122    /// Match the arguments.
123    pub fn apply(&self, args: Vec<Value>) -> Result<Map, ArguError> {
124        let mut out = Map::new();
125        let mut idx = 0usize;
126        let require = |n: &Node, v: Value, key: &str| -> Result<Value, ArguError> {
127            let (val, verr) = run(n, v, key, false);
128            if verr.has_any() {
129                return Err(ArguError(format!("{}: {}", self.prefix, verr)));
130            }
131            Ok(val)
132        };
133        for (key, n) in self.keys.iter().zip(self.nodes.iter()) {
134            if is_rest(n) {
135                // The arguments that remain, which is none at all when the
136                // rest comes last and the caller stopped: the canonical Argu
137                // leaves the slot an empty list.
138                let rem: Vec<Value> = args[idx.min(args.len())..].to_vec();
139                let child = n.arr_rest.as_deref().unwrap();
140                let mut validated = Vec::with_capacity(rem.len());
141                for v in rem {
142                    validated.push(require(child, v, key)?);
143                }
144                out.insert(key.clone(), Value::Arr(validated));
145                idx = args.len();
146            } else if n.skippable {
147                if idx >= args.len() {
148                    out.insert(key.clone(), Value::Undefined);
149                    continue;
150                }
151                // The slot is taken when the argument matches, and skipped
152                // otherwise.
153                let mut tester = n.clone();
154                tester.skippable = false;
155                tester.silent = false;
156                let (_, probe) = run(&tester, args[idx].clone(), "", true);
157                if !probe.has_any() {
158                    out.insert(key.clone(), require(&tester, args[idx].clone(), key)?);
159                    idx += 1;
160                } else {
161                    out.insert(key.clone(), Value::Undefined);
162                }
163            } else {
164                let v = args.get(idx).cloned().unwrap_or(Value::Undefined);
165                out.insert(key.clone(), require(n, v, key)?);
166                if idx < args.len() {
167                    idx += 1;
168                }
169            }
170        }
171        if idx < args.len() {
172            let expected = self.nodes.iter().filter(|n| !is_rest(n)).count();
173            return Err(ArguError(format!(
174                "{}: Too many arguments for type signature (was {}, expected {})",
175                self.prefix,
176                args.len(),
177                expected
178            )));
179        }
180        Ok(out)
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187    use crate::builders::{rest, skip};
188    use crate::node::Token;
189
190    #[test]
191    fn matches_positional_arguments() {
192        let argu = Argu::new("mylib");
193        let sig = argu
194            .signature(
195                "foo",
196                [
197                    ("a", Spec::from(Token::Number)),
198                    ("b", Spec::from(Token::String)),
199                ],
200            )
201            .unwrap();
202        let out = sig.apply(vec![Value::from(2), Value::from("x")]).unwrap();
203        assert_eq!(
204            serde_json::Value::from(Value::Obj(out)),
205            serde_json::json!({"a": 2, "b": "x"})
206        );
207        assert_eq!(
208            sig.apply(vec![Value::from(2)]).unwrap_err().0,
209            "mylib (foo): Validation failed for property \"b\" because the property is missing."
210        );
211        assert_eq!(
212            sig.apply(vec![Value::from("z"), Value::from("x")]).unwrap_err().0,
213            "mylib (foo): Validation failed for property \"a\" with string \"z\" because the string is not of type number."
214        );
215        assert_eq!(
216            sig.apply(vec![Value::from(1), Value::from("x"), Value::from(3)])
217                .unwrap_err()
218                .0,
219            "mylib (foo): Too many arguments for type signature (was 3, expected 2)"
220        );
221        // A skip slot shifts.
222        let out = argu
223            .validate(
224                vec![Value::from("x")],
225                "",
226                [
227                    ("a", Spec::from(skip(Token::Number))),
228                    ("b", Spec::from(Token::String)),
229                ],
230            )
231            .unwrap();
232        assert_eq!(
233            serde_json::Value::from(Value::Obj(out)),
234            serde_json::json!({"b": "x"})
235        );
236        let out = argu
237            .validate(
238                vec![Value::from(1), Value::from("x")],
239                "",
240                [
241                    ("a", Spec::from(skip(Token::Number))),
242                    ("b", Spec::from(Token::String)),
243                ],
244            )
245            .unwrap();
246        assert_eq!(
247            serde_json::Value::from(Value::Obj(out)),
248            serde_json::json!({"a": 1, "b": "x"})
249        );
250        let out = argu
251            .validate(vec![], "", [("a", Spec::from(skip(Token::Number)))])
252            .unwrap();
253        assert_eq!(
254            serde_json::Value::from(Value::Obj(out)),
255            serde_json::json!({})
256        );
257        // A rest slot captures the tail; none at all is one undefined.
258        let out = argu
259            .validate(
260                vec![Value::from(1), Value::from(2), Value::from(3)],
261                "",
262                [
263                    ("a", Spec::from(Token::Number)),
264                    (
265                        "d",
266                        Spec::from(rest(Token::Number, crate::spec::arr::<Spec>([]))),
267                    ),
268                ],
269            )
270            .unwrap();
271        assert_eq!(
272            serde_json::Value::from(Value::Obj(out)),
273            serde_json::json!({"a": 1, "d": [2, 3]})
274        );
275        let out = argu
276            .validate(
277                vec![Value::from(1)],
278                "",
279                [
280                    ("a", Spec::from(Token::Number)),
281                    (
282                        "d",
283                        Spec::from(rest(Token::Any, crate::spec::arr::<Spec>([]))),
284                    ),
285                ],
286            )
287            .unwrap();
288        assert_eq!(
289            serde_json::Value::from(Value::Obj(out)),
290            serde_json::json!({"a": 1, "d": []})
291        );
292        assert!(argu
293            .validate(
294                vec![Value::from(1), Value::from("x")],
295                "",
296                [
297                    ("a", Spec::from(Token::Number)),
298                    (
299                        "d",
300                        Spec::from(rest(Token::Number, crate::spec::arr::<Spec>([])))
301                    )
302                ],
303            )
304            .is_err());
305        assert_eq!(
306            argu.signature("f", Vec::<(String, Spec)>::new())
307                .unwrap_err()
308                .0,
309            "mylib: empty argument spec"
310        );
311        assert_eq!(format!("{}", ArguError("e".into())), "e");
312    }
313}