Skip to main content

shape/
value.rs

1//! The value shape validates: what JSON can carry, plus the four things the
2//! canonical behaviour needs and JSON cannot say (an absent value, `NaN`, a
3//! date, a bigint), with object keys in insertion order.
4
5use indexmap::IndexMap;
6use num_bigint::BigInt;
7use std::fmt;
8
9/// An object: keys in insertion order, as a JavaScript object keeps them.
10pub type Map = IndexMap<String, Value>;
11
12/// A value under validation, or produced by it.
13#[derive(Clone, Debug, PartialEq, Default)]
14pub enum Value {
15    /// No value at all: a missing property, an absent argument. JavaScript's
16    /// `undefined`, which JSON cannot carry.
17    #[default]
18    Undefined,
19    /// A present null.
20    Null,
21    Bool(bool),
22    /// Every number is a double, as in JavaScript; `NaN` is a number here.
23    Num(f64),
24    Str(String),
25    BigInt(BigInt),
26    Arr(Vec<Value>),
27    Obj(Map),
28    /// A date, as milliseconds since the epoch, as a JavaScript `Date` holds it.
29    Date(i64),
30    /// A function value: opaque, identified so two can be told apart.
31    Func(u64),
32}
33
34impl Value {
35    pub fn is_undefined(&self) -> bool {
36        matches!(self, Value::Undefined)
37    }
38
39    pub fn is_null(&self) -> bool {
40        matches!(self, Value::Null)
41    }
42
43    /// The `typeof` of the value as JavaScript reports it (`undefined` and
44    /// `null` both read as "value" in messages; see `value_kind`).
45    pub fn type_of(&self) -> &'static str {
46        match self {
47            Value::Undefined => "undefined",
48            Value::Null => "object",
49            Value::Bool(_) => "boolean",
50            Value::Num(_) => "number",
51            Value::Str(_) => "string",
52            Value::BigInt(_) => "bigint",
53            Value::Arr(_) => "object",
54            Value::Obj(_) => "object",
55            Value::Date(_) => "object",
56            Value::Func(_) => "function",
57        }
58    }
59
60    pub fn as_obj(&self) -> Option<&Map> {
61        match self {
62            Value::Obj(m) => Some(m),
63            _ => None,
64        }
65    }
66
67    pub fn as_arr(&self) -> Option<&Vec<Value>> {
68        match self {
69            Value::Arr(a) => Some(a),
70            _ => None,
71        }
72    }
73
74    pub fn as_str(&self) -> Option<&str> {
75        match self {
76            Value::Str(s) => Some(s),
77            _ => None,
78        }
79    }
80
81    pub fn as_f64(&self) -> Option<f64> {
82        match self {
83            Value::Num(n) => Some(*n),
84            _ => None,
85        }
86    }
87}
88
89impl From<bool> for Value {
90    fn from(b: bool) -> Self {
91        Value::Bool(b)
92    }
93}
94impl From<f64> for Value {
95    fn from(n: f64) -> Self {
96        Value::Num(n)
97    }
98}
99impl From<i64> for Value {
100    fn from(n: i64) -> Self {
101        Value::Num(n as f64)
102    }
103}
104impl From<i32> for Value {
105    fn from(n: i32) -> Self {
106        Value::Num(n as f64)
107    }
108}
109impl From<u32> for Value {
110    fn from(n: u32) -> Self {
111        Value::Num(n as f64)
112    }
113}
114impl From<usize> for Value {
115    fn from(n: usize) -> Self {
116        Value::Num(n as f64)
117    }
118}
119impl From<&str> for Value {
120    fn from(s: &str) -> Self {
121        Value::Str(s.to_string())
122    }
123}
124impl From<String> for Value {
125    fn from(s: String) -> Self {
126        Value::Str(s)
127    }
128}
129impl From<Vec<Value>> for Value {
130    fn from(a: Vec<Value>) -> Self {
131        Value::Arr(a)
132    }
133}
134impl From<Map> for Value {
135    fn from(m: Map) -> Self {
136        Value::Obj(m)
137    }
138}
139impl From<BigInt> for Value {
140    fn from(b: BigInt) -> Self {
141        Value::BigInt(b)
142    }
143}
144
145/// A number as JavaScript's `Number.prototype.toString` prints it: the
146/// shortest digits that read back as the same double, an integer without a
147/// point, an exponent below 1e-6 and from 1e21.
148pub fn js_number(x: f64) -> String {
149    if x.is_nan() {
150        return "NaN".to_string();
151    }
152    if x == 0.0 {
153        return "0".to_string();
154    }
155    if x.is_infinite() {
156        return if x > 0.0 { "Infinity" } else { "-Infinity" }.to_string();
157    }
158    // A whole number below 2^53 is exact, and prints as its digits.
159    if x.abs() < 9007199254740992.0 && x == x.trunc() {
160        return (x as i64).to_string();
161    }
162    let neg = x < 0.0;
163    // Shortest round-trip digits, in scientific form: "d.ddde±x".
164    let sci = format!("{:e}", x.abs());
165    let (mant, exp) = sci.split_once('e').unwrap_or((&sci, "0"));
166    let e: i32 = exp.parse().unwrap_or(0);
167    let digits: String = mant.chars().filter(|c| *c != '.').collect();
168    let k = digits.len() as i32;
169    // n is the position of the decimal point relative to the digits.
170    let n = e + 1;
171    let body = if k <= n && n <= 21 {
172        format!("{}{}", digits, "0".repeat((n - k) as usize))
173    } else if 0 < n && n <= 21 {
174        let (a, b) = digits.split_at(n as usize);
175        format!("{}.{}", a, b)
176    } else if -6 < n && n <= 0 {
177        format!("0.{}{}", "0".repeat((-n) as usize), digits)
178    } else {
179        let ee = n - 1;
180        let esign = if ee < 0 { "-" } else { "+" };
181        if k == 1 {
182            format!("{}e{}{}", digits, esign, ee.abs())
183        } else {
184            let (a, b) = digits.split_at(1);
185            format!("{}.{}e{}{}", a, b, esign, ee.abs())
186        }
187    };
188    if neg {
189        format!("-{}", body)
190    } else {
191        body
192    }
193}
194
195/// A date as `Date.prototype.toISOString` prints it: UTC, millisecond precision.
196pub fn js_date_string(ms: i64) -> String {
197    let days = ms.div_euclid(86_400_000);
198    let rem = ms.rem_euclid(86_400_000);
199    let (y, m, d) = civil_from_days(days);
200    let h = rem / 3_600_000;
201    let mi = (rem / 60_000) % 60;
202    let s = (rem / 1000) % 60;
203    let milli = rem % 1000;
204    let year = if (0..=9999).contains(&y) {
205        format!("{:04}", y)
206    } else if y < 0 {
207        format!("-{:06}", -y)
208    } else {
209        format!("+{:06}", y)
210    };
211    format!(
212        "{}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z",
213        year, m, d, h, mi, s, milli
214    )
215}
216
217// Days since 1970-01-01 to a proleptic Gregorian civil date.
218fn civil_from_days(z: i64) -> (i64, u32, u32) {
219    let z = z + 719_468;
220    let era = z.div_euclid(146_097);
221    let doe = z.rem_euclid(146_097);
222    let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
223    let y = yoe + era * 400;
224    let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
225    let mp = (5 * doy + 2) / 153;
226    let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
227    let m = if mp < 10 { mp + 3 } else { mp - 9 } as u32;
228    (if m <= 2 { y + 1 } else { y }, m, d)
229}
230
231/// A string as `JSON.stringify` prints it, quotes included: no HTML escaping.
232pub fn json_text(s: &str) -> String {
233    let mut out = String::with_capacity(s.len() + 2);
234    out.push('"');
235    for c in s.chars() {
236        match c {
237            '"' => out.push_str("\\\""),
238            '\\' => out.push_str("\\\\"),
239            '\n' => out.push_str("\\n"),
240            '\r' => out.push_str("\\r"),
241            '\t' => out.push_str("\\t"),
242            '\u{8}' => out.push_str("\\b"),
243            '\u{c}' => out.push_str("\\f"),
244            c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
245            c => out.push(c),
246        }
247    }
248    out.push('"');
249    out
250}
251
252/// A value as `JSON.stringify` prints it after shape's replacer: keys in
253/// insertion order, no spaces, `NaN` as the text NaN, an undefined element as
254/// null and an undefined property omitted, a date as its ISO text.
255pub fn json_render(v: &Value) -> String {
256    match v {
257        Value::Undefined | Value::Null => "null".to_string(),
258        Value::Bool(b) => b.to_string(),
259        Value::Num(n) => {
260            if n.is_nan() {
261                "\"NaN\"".to_string()
262            } else if n.is_infinite() {
263                "null".to_string()
264            } else {
265                js_number(*n)
266            }
267        }
268        Value::Str(s) => json_text(s),
269        Value::BigInt(b) => format!("\"{}\"", b),
270        Value::Date(ms) => format!("\"{}\"", js_date_string(*ms)),
271        Value::Func(_) => "null".to_string(),
272        Value::Arr(a) => {
273            let parts: Vec<String> = a.iter().map(json_render).collect();
274            format!("[{}]", parts.join(","))
275        }
276        Value::Obj(m) => {
277            let parts: Vec<String> = m
278                .iter()
279                .filter(|(_, v)| !v.is_undefined())
280                .map(|(k, v)| format!("{}:{}", json_text(k), json_render(v)))
281                .collect();
282            format!("{{{}}}", parts.join(","))
283        }
284    }
285}
286
287/// The limit a rendered value is clipped to in a message.
288pub const ERR_VALUE_LIMIT: usize = 111;
289
290/// Clip a rendering to `limit` characters, the last three an ellipsis.
291pub fn truncate_text(s: &str, limit: usize) -> String {
292    let n = s.chars().count();
293    if n <= limit {
294        return s.to_string();
295    }
296    if limit < 3 {
297        return s.chars().take(limit).collect();
298    }
299    let head: String = s.chars().take(limit - 3).collect();
300    format!("{}...", head)
301}
302
303/// A value as a message names it: its JSON with the quotes stripped, clipped.
304pub fn value_to_string(v: &Value) -> String {
305    match v {
306        Value::Undefined => "undefined".to_string(),
307        Value::Null => "null".to_string(),
308        Value::Str(s) => {
309            // json_text's escapes with every quote removed, in one pass: an
310            // escaped quote leaves its backslash.
311            let mut out = String::with_capacity(s.len());
312            for c in s.chars() {
313                match c {
314                    '"' => out.push('\\'),
315                    '\\' => out.push_str("\\\\"),
316                    '\n' => out.push_str("\\n"),
317                    '\r' => out.push_str("\\r"),
318                    '\t' => out.push_str("\\t"),
319                    c => match c as u32 {
320                        8 => out.push_str("\\b"),
321                        12 => out.push_str("\\f"),
322                        n if n < 0x20 => out.push_str(&format!("\\u{:04x}", n)),
323                        _ => out.push(c),
324                    },
325                }
326            }
327            if out.chars().count() <= ERR_VALUE_LIMIT {
328                out
329            } else {
330                truncate_text(&out, ERR_VALUE_LIMIT)
331            }
332        }
333        Value::Num(n) => js_number(*n),
334        Value::Bool(b) => b.to_string(),
335        Value::BigInt(b) => b.to_string(),
336        Value::Date(ms) => js_date_string(*ms),
337        Value::Func(_) => "function".to_string(),
338        Value::Arr(_) | Value::Obj(_) => {
339            truncate_text(&json_render(v).replace('"', ""), ERR_VALUE_LIMIT)
340        }
341    }
342}
343
344/// The kind a message calls a value: "value" for nothing, null or NaN,
345/// otherwise its `typeof`, with arrays told from objects.
346pub fn value_kind(v: &Value) -> &'static str {
347    match v {
348        Value::Undefined | Value::Null => "value",
349        Value::Num(n) if n.is_nan() => "value",
350        Value::Num(_) => "number",
351        Value::Str(_) => "string",
352        Value::Bool(_) => "boolean",
353        Value::BigInt(_) => "bigint",
354        Value::Arr(_) => "array",
355        Value::Obj(_) | Value::Date(_) => "object",
356        Value::Func(_) => "function",
357    }
358}
359
360impl fmt::Display for Value {
361    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
362        f.write_str(&value_to_string(self))
363    }
364}
365
366/// Whether a number is an integer as `Number.isInteger` judges it.
367pub fn is_integer(x: f64) -> bool {
368    // Past 2^53 every finite double is whole; below it the i64 round trip
369    // is exact, and cheaper than trunc.
370    x.is_finite() && (x.abs() >= 9007199254740992.0 || (x as i64) as f64 == x)
371}
372
373#[cfg(feature = "serde")]
374mod serde_conv {
375    use super::{Map, Value};
376
377    impl From<serde_json::Value> for Value {
378        fn from(v: serde_json::Value) -> Self {
379            match v {
380                serde_json::Value::Null => Value::Null,
381                serde_json::Value::Bool(b) => Value::Bool(b),
382                serde_json::Value::Number(n) => Value::Num(n.as_f64().unwrap_or(f64::NAN)),
383                serde_json::Value::String(s) => Value::Str(s),
384                serde_json::Value::Array(a) => Value::Arr(a.into_iter().map(Value::from).collect()),
385                serde_json::Value::Object(m) => {
386                    let mut out = Map::with_capacity(m.len());
387                    for (k, v) in m {
388                        out.insert(k, Value::from(v));
389                    }
390                    Value::Obj(out)
391                }
392            }
393        }
394    }
395
396    impl From<Value> for serde_json::Value {
397        /// As `JSON.stringify` would carry it: an undefined property is dropped,
398        /// an undefined element and a NaN become null, a date its ISO text.
399        fn from(v: Value) -> Self {
400            match v {
401                Value::Undefined | Value::Null => serde_json::Value::Null,
402                Value::Bool(b) => serde_json::Value::Bool(b),
403                // An integral number within the safe range is carried as an
404                // integer, so `1` compares equal to `json!(1)`.
405                Value::Num(n) if n.fract() == 0.0 && n.abs() < 9007199254740992.0 => {
406                    serde_json::Value::from(n as i64)
407                }
408                Value::Num(n) => serde_json::Number::from_f64(n)
409                    .map(serde_json::Value::Number)
410                    .unwrap_or(serde_json::Value::Null),
411                Value::Str(s) => serde_json::Value::String(s),
412                Value::BigInt(b) => serde_json::Value::String(b.to_string()),
413                Value::Date(ms) => serde_json::Value::String(super::js_date_string(ms)),
414                Value::Func(_) => serde_json::Value::Null,
415                Value::Arr(a) => {
416                    serde_json::Value::Array(a.into_iter().map(serde_json::Value::from).collect())
417                }
418                Value::Obj(m) => {
419                    let mut out = serde_json::Map::with_capacity(m.len());
420                    for (k, v) in m {
421                        if !v.is_undefined() {
422                            out.insert(k, serde_json::Value::from(v));
423                        }
424                    }
425                    serde_json::Value::Object(out)
426                }
427            }
428        }
429    }
430}
431
432#[cfg(test)]
433mod tests {
434    use super::*;
435
436    #[test]
437    fn numbers_print_as_javascript_does() {
438        let cases: [(f64, &str); 19] = [
439            (0.0, "0"),
440            (1.0, "1"),
441            (-1.0, "-1"),
442            (-1.5, "-1.5"),
443            (-1e21, "-1e+21"),
444            (-2.5e-7, "-2.5e-7"),
445            (9007199254740993.0, "9007199254740992"),
446            (4503599627370496.5, "4503599627370496"),
447            (1.5, "1.5"),
448            (0.1, "0.1"),
449            (100.0, "100"),
450            (1e21, "1e+21"),
451            (1e20, "100000000000000000000"),
452            (123456789012345680000.0, "123456789012345680000"),
453            (1e-7, "1e-7"),
454            (0.000001, "0.000001"),
455            (1.5e-7, "1.5e-7"),
456            (1.2345e25, "1.2345e+25"),
457            (f64::NAN, "NaN"),
458        ];
459        for (x, want) in cases {
460            assert_eq!(js_number(x), want, "{}", x);
461        }
462        assert_eq!(js_number(f64::INFINITY), "Infinity");
463        assert_eq!(js_number(f64::NEG_INFINITY), "-Infinity");
464    }
465
466    #[test]
467    fn dates_print_as_iso() {
468        assert_eq!(js_date_string(0), "1970-01-01T00:00:00.000Z");
469        assert_eq!(
470            js_date_string(4_765_132_800_000),
471            "2121-01-01T00:00:00.000Z"
472        );
473        assert_eq!(js_date_string(-1), "1969-12-31T23:59:59.999Z");
474        assert_eq!(
475            js_date_string(-62_198_755_200_000),
476            "-000001-01-01T00:00:00.000Z"
477        );
478        assert_eq!(
479            js_date_string(253_402_300_800_000),
480            "+010000-01-01T00:00:00.000Z"
481        );
482    }
483
484    #[test]
485    fn strings_render_with_json_escapes_and_no_quotes() {
486        assert_eq!(json_text("a\"b\\c\n\u{1}"), "\"a\\\"b\\\\c\\n\\u0001\"");
487        assert_eq!(value_to_string(&Value::from("a\"b\\c")), "a\\b\\\\c");
488        // The other escapes, as json_text writes them.
489        assert_eq!(
490            value_to_string(&Value::from("r\rt\tb\u{8}f\u{c}c\u{1}x")),
491            "r\\rt\\tb\\bf\\fc\\u0001x"
492        );
493        assert_eq!(
494            value_to_string(&Value::from("r\rt\tb\u{8}f\u{c}c\u{1}x")),
495            json_text("r\rt\tb\u{8}f\u{c}c\u{1}x").replace('"', "")
496        );
497        let long = "x".repeat(200);
498        let t = value_to_string(&Value::from(long.as_str()));
499        assert_eq!(t.len(), ERR_VALUE_LIMIT);
500        assert!(t.ends_with("..."));
501        assert_eq!(truncate_text("abcdef", 2), "ab");
502    }
503
504    #[test]
505    fn containers_render_in_insertion_order() {
506        let mut m = Map::new();
507        m.insert("b".into(), Value::from(1));
508        m.insert(
509            "a".into(),
510            Value::Arr(vec![
511                Value::Undefined,
512                Value::from("x"),
513                Value::Num(f64::NAN),
514            ]),
515        );
516        m.insert("u".into(), Value::Undefined);
517        m.insert("d".into(), Value::Date(0));
518        assert_eq!(
519            value_to_string(&Value::Obj(m)),
520            "{b:1,a:[null,x,NaN],d:1970-01-01T00:00:00.000Z}"
521        );
522        assert_eq!(json_render(&Value::Num(f64::INFINITY)), "null");
523        assert_eq!(value_kind(&Value::Arr(vec![])), "array");
524        assert_eq!(value_kind(&Value::Num(f64::NAN)), "value");
525        assert_eq!(value_kind(&Value::Date(0)), "object");
526        assert_eq!(value_to_string(&Value::BigInt(BigInt::from(7))), "7");
527        assert_eq!(json_render(&Value::BigInt(BigInt::from(7))), "\"7\"");
528        assert_eq!(value_to_string(&Value::Func(1)), "function");
529        assert_eq!(value_to_string(&Value::Date(0)), "1970-01-01T00:00:00.000Z");
530        assert_eq!(json_text("\r\t"), "\"\\r\\t\"");
531        assert_eq!(json_render(&Value::Func(1)), "null");
532        assert_eq!(value_kind(&Value::Func(1)), "function");
533        assert_eq!(value_kind(&Value::BigInt(BigInt::from(1))), "bigint");
534        assert_eq!(Value::Func(1).type_of(), "function");
535        assert_eq!(Value::Null.type_of(), "object");
536        assert_eq!(Value::Undefined.type_of(), "undefined");
537        assert_eq!(Value::BigInt(BigInt::from(1)).type_of(), "bigint");
538        assert_eq!(Value::Date(0).type_of(), "object");
539        assert_eq!(format!("{}", Value::Null), "null");
540        assert_eq!(format!("{}", Value::Undefined), "undefined");
541        assert_eq!(format!("{}", Value::Bool(true)), "true");
542        assert!(is_integer(3.0) && !is_integer(3.5) && !is_integer(f64::NAN));
543    }
544
545    #[test]
546    fn conversions() {
547        assert_eq!(Value::from(true), Value::Bool(true));
548        assert_eq!(Value::from(2i64), Value::Num(2.0));
549        assert_eq!(Value::from(2i32), Value::Num(2.0));
550        assert_eq!(Value::from(2u32), Value::Num(2.0));
551        assert_eq!(Value::from(2usize), Value::Num(2.0));
552        assert_eq!(Value::from(String::from("s")), Value::Str("s".into()));
553        assert_eq!(
554            Value::from(vec![Value::Null]),
555            Value::Arr(vec![Value::Null])
556        );
557        assert_eq!(Value::from(Map::new()), Value::Obj(Map::new()));
558        assert_eq!(Value::from(BigInt::from(1)), Value::BigInt(BigInt::from(1)));
559        assert_eq!(Value::default(), Value::Undefined);
560        assert!(Value::Null.is_null());
561        assert_eq!(Value::from(1.5).as_f64(), Some(1.5));
562        assert_eq!(Value::from("x").as_str(), Some("x"));
563        assert!(Value::Null.as_str().is_none() && Value::Null.as_f64().is_none());
564        assert!(Value::Null.as_obj().is_none() && Value::Null.as_arr().is_none());
565        assert!(Value::Obj(Map::new()).as_obj().is_some());
566        assert!(Value::Arr(vec![]).as_arr().is_some());
567    }
568
569    #[cfg(feature = "serde")]
570    #[test]
571    fn serde_round_trip() {
572        let j: serde_json::Value =
573            serde_json::from_str(r#"{"b":1,"a":[null,"x",true],"c":{"z":1.5}}"#).unwrap();
574        let v = Value::from(j.clone());
575        let keys: Vec<&String> = v.as_obj().unwrap().keys().collect();
576        assert_eq!(keys, vec!["b", "a", "c"]);
577        assert_eq!(serde_json::Value::from(v), j);
578        let mut m = Map::new();
579        m.insert("u".into(), Value::Undefined);
580        m.insert("n".into(), Value::Num(f64::NAN));
581        m.insert("d".into(), Value::Date(0));
582        m.insert("b".into(), Value::BigInt(BigInt::from(3)));
583        m.insert("f".into(), Value::Func(0));
584        m.insert("e".into(), Value::Arr(vec![Value::Undefined]));
585        let out = serde_json::Value::from(Value::Obj(m)).to_string();
586        assert_eq!(
587            out,
588            r#"{"n":null,"d":"1970-01-01T00:00:00.000Z","b":"3","f":null,"e":[null]}"#
589        );
590    }
591
592    #[test]
593    fn remaining_kinds_render() {
594        let m = Map::new();
595        let cases: Vec<(Value, &str, &str)> = vec![
596            (Value::Num(1.0), "number", "number"),
597            (Value::Str("s".into()), "string", "string"),
598            (Value::BigInt(BigInt::from(2)), "bigint", "bigint"),
599            (Value::Arr(vec![]), "object", "array"),
600            (Value::Obj(m), "object", "object"),
601            (Value::Date(0), "object", "object"),
602            (Value::Func(1), "function", "function"),
603            (Value::Bool(true), "boolean", "boolean"),
604            (Value::Null, "object", "value"),
605            (Value::Undefined, "undefined", "value"),
606            (Value::Num(f64::NAN), "number", "value"),
607        ];
608        for (v, ty, kind) in cases {
609            assert_eq!(v.type_of(), ty, "{:?}", v);
610            assert_eq!(value_kind(&v), kind, "{:?}", v);
611        }
612        assert_eq!(value_to_string(&Value::Func(1)), "function");
613        assert_eq!(value_to_string(&Value::Date(0)), "1970-01-01T00:00:00.000Z");
614        assert_eq!(json_text("\r\t"), "\"\\r\\t\"");
615        assert_eq!(json_text("a\u{8}b\u{c}c\u{1}d"), "\"a\\bb\\fc\\u0001d\"");
616    }
617}