Skip to main content

shape/
stringify.rs

1//! A node rendered as the spec text it stands for: type names for required
2//! nodes, values for optional ones, the builder chain as a suffix. This is
3//! what a composite message names its branches with.
4
5use crate::node::{Kind, ListMode, Node};
6use crate::value::{js_date_string, js_number, json_text, Value};
7
8/// Render a node. `inline` is the canonical dequote flag: inside a composite
9/// message a string value is written bare, on its own it keeps its quotes.
10pub fn stringify_node(n: &Node, inline: bool) -> String {
11    match n.kind {
12        Kind::String => suffix(type_or_value(n, "String", inline), n),
13        Kind::Number => suffix(type_or_value(n, "Number", inline), n),
14        Kind::Boolean => suffix(type_or_value(n, "Boolean", inline), n),
15        Kind::Integer => suffix(type_or_value(n, "Integer", inline), n),
16        Kind::BigInt => suffix(type_or_value(n, "BigInt", inline), n),
17        Kind::Date => {
18            let base = match n.default {
19                Value::Date(ms) if !n.required && n.has_default => {
20                    let iso = js_date_string(ms);
21                    if inline {
22                        iso
23                    } else {
24                        format!("\"{}\"", iso)
25                    }
26                }
27                _ => "Date".to_string(),
28            };
29            suffix(base, n)
30        }
31        Kind::Null => "null".to_string(),
32        Kind::NaN => "NaN".to_string(),
33        Kind::Any => {
34            // The Any token carries no default at all: it renders as "Any".
35            let base = if n.has_default && !n.default.is_undefined() {
36                format!("Any({})", inline_value(&n.default))
37            } else {
38                "Any".to_string()
39            };
40            let out = suffix(base.clone(), n);
41            // A node with no asserted type exists only to carry its
42            // builders, and renders as that chain alone: "Min(2)", not
43            // "Any.Min(2)".
44            match out.strip_prefix("Any.") {
45                Some(rest) if base == "Any" => rest.to_string(),
46                _ => out,
47            }
48        }
49        Kind::Never => suffix("Never".to_string(), n),
50        Kind::Regexp => match &n.regexp {
51            Some(_) => suffix(format!("/{}/", n.regexp_src), n),
52            None => suffix("Regexp".to_string(), n),
53        },
54        Kind::Check => suffix("Check".to_string(), n),
55        Kind::Function => suffix("Function".to_string(), n),
56        Kind::List => {
57            if let Some(d) = &n.disc {
58                return suffix(format!("Discriminated({},{})", d.tag, d.tags.join(",")), n);
59            }
60            let mode = match n.list_mode {
61                ListMode::Some => "Some",
62                ListMode::All => "All",
63                _ => "One",
64            };
65            let parts: Vec<String> = n.list.iter().map(|sn| stringify_node(sn, true)).collect();
66            suffix(format!("{}({})", mode, parts.join(",")), n)
67        }
68        Kind::Array => {
69            let mut parts: Vec<String> = Vec::new();
70            if !n.arr_children.is_empty() {
71                parts.extend(n.arr_children.iter().map(|sn| stringify_node(sn, true)));
72            } else if let Some(c) = &n.arr_child {
73                parts.push(stringify_node(c, true));
74            }
75            if let Some(r) = &n.arr_rest {
76                parts.push(format!("...{}", stringify_node(r, true)));
77            }
78            suffix(format!("[{}]", parts.join(", ")), n)
79        }
80        Kind::Object => {
81            let parts: Vec<String> = n
82                .obj_children
83                .iter()
84                .map(|(k, cn)| format!("{}: {}", k, stringify_node(cn, true)))
85                .collect();
86            let mut body = format!("{{{}}}", parts.join(", "));
87            match &n.obj_rest {
88                Some(rest) if rest.kind != Kind::Any => {
89                    body.push_str(&format!(".Child({})", stringify_node(rest, true)));
90                }
91                Some(_) => body.push_str(".Open()"),
92                None => {}
93            }
94            suffix(body, n)
95        }
96    }
97}
98
99/// The builder chain a node carries, as `.Name(args)` after the base. A
100/// required scalar is just its type name, never `.Required()`; Skip, Ignore
101/// and Empty change whether a value is demanded, not what shape it has, and
102/// are left out, as the canonical rendering leaves them.
103fn suffix(base: String, n: &Node) -> String {
104    let mut out = base;
105    for v in n.befores.iter().chain(n.afters.iter()) {
106        if let Some(s) = &v.suffix {
107            out.push('.');
108            out.push_str(s);
109        }
110    }
111    out
112}
113
114/// A typed node the canonical way: a required node shows its type name, an
115/// unrequired one the value it would produce, because that value is what
116/// the schema stands for there.
117fn type_or_value(n: &Node, type_name: &str, inline: bool) -> String {
118    if n.required || !n.has_default {
119        return type_name.to_string();
120    }
121    if let Value::Str(s) = &n.default {
122        if !inline {
123            return json_text(s);
124        }
125    }
126    inline_value(&n.default)
127}
128
129/// A value inside a rendering: strings bare, numbers as JavaScript prints
130/// them, everything else as JSON.
131pub(crate) fn inline_value(v: &Value) -> String {
132    match v {
133        Value::Str(s) => s.clone(),
134        Value::Num(n) => js_number(*n),
135        Value::Undefined => "undefined".to_string(),
136        other => crate::value::json_render(other),
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143    use crate::builders::*;
144    use crate::node::Token;
145    use crate::spec::{arr, obj, Spec};
146    use regex::Regex;
147
148    #[test]
149    fn renders_types_values_and_chains() {
150        assert_eq!(stringify_node(&buildize(Token::String), false), "String");
151        assert_eq!(stringify_node(&buildize("x"), false), "\"x\"");
152        assert_eq!(stringify_node(&buildize("x"), true), "x");
153        assert_eq!(stringify_node(&buildize(1.5), false), "1.5");
154        assert_eq!(stringify_node(&buildize(true), false), "true");
155        assert_eq!(stringify_node(&buildize(Token::Integer), false), "Integer");
156        assert_eq!(stringify_node(&buildize(Token::BigInt), false), "BigInt");
157        assert_eq!(stringify_node(&buildize(Token::Date), false), "Date");
158        assert_eq!(
159            stringify_node(&buildize(Spec::Value(Value::Date(0))), false),
160            "\"1970-01-01T00:00:00.000Z\""
161        );
162        assert_eq!(
163            stringify_node(&buildize(Spec::Value(Value::Date(0))), true),
164            "1970-01-01T00:00:00.000Z"
165        );
166        assert_eq!(
167            stringify_node(&buildize(Spec::Value(Value::Null)), false),
168            "null"
169        );
170        assert_eq!(stringify_node(&buildize(f64::NAN), false), "NaN");
171        assert_eq!(stringify_node(&any(), false), "Any");
172        assert_eq!(stringify_node(&min(2, any()), false), "Min(2)");
173        assert_eq!(
174            stringify_node(&min(2, Token::Number), false),
175            "Number.Min(2)"
176        );
177        let mut anyd = any();
178        anyd.has_default = true;
179        anyd.default = Value::from(3);
180        assert_eq!(stringify_node(&anyd, false), "Any(3)");
181        assert_eq!(stringify_node(&never(1), false), "Never");
182        assert_eq!(
183            stringify_node(&buildize(Regex::new("^a").unwrap()), false),
184            "/^a/"
185        );
186        let mut re = buildize(Token::String);
187        re.kind = Kind::Regexp;
188        assert_eq!(stringify_node(&re, false), "Regexp");
189        assert_eq!(
190            stringify_node(&check(|_, _| true, any()), false),
191            "Check.Check()"
192        );
193        assert_eq!(
194            stringify_node(&buildize(Token::Function), false),
195            "Function"
196        );
197        assert_eq!(
198            stringify_node(&one([Spec::from(Token::Number), Spec::from("a")]), false),
199            "One(Number,a)"
200        );
201        assert_eq!(
202            stringify_node(&some([Spec::from(Token::Number)]), false),
203            "Some(Number)"
204        );
205        assert_eq!(
206            stringify_node(&all([Spec::from(Token::Number)]), false),
207            "All(Number)"
208        );
209        assert_eq!(stringify_node(&buildize(Token::Array), false), "[]");
210        assert_eq!(
211            stringify_node(
212                &buildize(arr([Spec::from(1), Spec::from(Token::String)])),
213                false
214            ),
215            "[1, String]"
216        );
217        assert_eq!(
218            stringify_node(&rest(Token::Number, arr::<Spec>([])), false),
219            "[...Number]"
220        );
221        assert_eq!(
222            stringify_node(
223                &buildize(obj([
224                    ("b", Spec::from(1)),
225                    ("a", Spec::from(Token::String))
226                ])),
227                false
228            ),
229            "{b: 1, a: String}"
230        );
231        assert_eq!(stringify_node(&buildize(Token::Object), false), "{}.Open()");
232        assert_eq!(
233            stringify_node(&child(Token::Number, Token::Object), false),
234            "{}.Child(Number)"
235        );
236        assert_eq!(inline_value(&Value::Undefined), "undefined");
237    }
238}