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    let neg = x < 0.0;
159    // Shortest round-trip digits, in scientific form: "d.ddde±x".
160    let sci = format!("{:e}", x.abs());
161    let (mant, exp) = sci.split_once('e').unwrap_or((&sci, "0"));
162    let e: i32 = exp.parse().unwrap_or(0);
163    let digits: String = mant.chars().filter(|c| *c != '.').collect();
164    let k = digits.len() as i32;
165    // n is the position of the decimal point relative to the digits.
166    let n = e + 1;
167    let body = if k <= n && n <= 21 {
168        format!("{}{}", digits, "0".repeat((n - k) as usize))
169    } else if 0 < n && n <= 21 {
170        let (a, b) = digits.split_at(n as usize);
171        format!("{}.{}", a, b)
172    } else if -6 < n && n <= 0 {
173        format!("0.{}{}", "0".repeat((-n) as usize), digits)
174    } else {
175        let ee = n - 1;
176        let esign = if ee < 0 { "-" } else { "+" };
177        if k == 1 {
178            format!("{}e{}{}", digits, esign, ee.abs())
179        } else {
180            let (a, b) = digits.split_at(1);
181            format!("{}.{}e{}{}", a, b, esign, ee.abs())
182        }
183    };
184    if neg {
185        format!("-{}", body)
186    } else {
187        body
188    }
189}
190
191/// A date as `Date.prototype.toISOString` prints it: UTC, millisecond precision.
192pub fn js_date_string(ms: i64) -> String {
193    let days = ms.div_euclid(86_400_000);
194    let rem = ms.rem_euclid(86_400_000);
195    let (y, m, d) = civil_from_days(days);
196    let h = rem / 3_600_000;
197    let mi = (rem / 60_000) % 60;
198    let s = (rem / 1000) % 60;
199    let milli = rem % 1000;
200    let year = if (0..=9999).contains(&y) {
201        format!("{:04}", y)
202    } else if y < 0 {
203        format!("-{:06}", -y)
204    } else {
205        format!("+{:06}", y)
206    };
207    format!(
208        "{}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z",
209        year, m, d, h, mi, s, milli
210    )
211}
212
213// Days since 1970-01-01 to a proleptic Gregorian civil date.
214fn civil_from_days(z: i64) -> (i64, u32, u32) {
215    let z = z + 719_468;
216    let era = z.div_euclid(146_097);
217    let doe = z.rem_euclid(146_097);
218    let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
219    let y = yoe + era * 400;
220    let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
221    let mp = (5 * doy + 2) / 153;
222    let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
223    let m = if mp < 10 { mp + 3 } else { mp - 9 } as u32;
224    (if m <= 2 { y + 1 } else { y }, m, d)
225}
226
227/// A string as `JSON.stringify` prints it, quotes included: no HTML escaping.
228pub fn json_text(s: &str) -> String {
229    let mut out = String::with_capacity(s.len() + 2);
230    out.push('"');
231    for c in s.chars() {
232        match c {
233            '"' => out.push_str("\\\""),
234            '\\' => out.push_str("\\\\"),
235            '\n' => out.push_str("\\n"),
236            '\r' => out.push_str("\\r"),
237            '\t' => out.push_str("\\t"),
238            '\u{8}' => out.push_str("\\b"),
239            '\u{c}' => out.push_str("\\f"),
240            c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
241            c => out.push(c),
242        }
243    }
244    out.push('"');
245    out
246}
247
248/// A value as `JSON.stringify` prints it after shape's replacer: keys in
249/// insertion order, no spaces, `NaN` as the text NaN, an undefined element as
250/// null and an undefined property omitted, a date as its ISO text.
251pub fn json_render(v: &Value) -> String {
252    match v {
253        Value::Undefined | Value::Null => "null".to_string(),
254        Value::Bool(b) => b.to_string(),
255        Value::Num(n) => {
256            if n.is_nan() {
257                "\"NaN\"".to_string()
258            } else if n.is_infinite() {
259                "null".to_string()
260            } else {
261                js_number(*n)
262            }
263        }
264        Value::Str(s) => json_text(s),
265        Value::BigInt(b) => format!("\"{}\"", b),
266        Value::Date(ms) => format!("\"{}\"", js_date_string(*ms)),
267        Value::Func(_) => "null".to_string(),
268        Value::Arr(a) => {
269            let parts: Vec<String> = a.iter().map(json_render).collect();
270            format!("[{}]", parts.join(","))
271        }
272        Value::Obj(m) => {
273            let parts: Vec<String> = m
274                .iter()
275                .filter(|(_, v)| !v.is_undefined())
276                .map(|(k, v)| format!("{}:{}", json_text(k), json_render(v)))
277                .collect();
278            format!("{{{}}}", parts.join(","))
279        }
280    }
281}
282
283/// The limit a rendered value is clipped to in a message.
284pub const ERR_VALUE_LIMIT: usize = 111;
285
286/// Clip a rendering to `limit` characters, the last three an ellipsis.
287pub fn truncate_text(s: &str, limit: usize) -> String {
288    let n = s.chars().count();
289    if n <= limit {
290        return s.to_string();
291    }
292    if limit < 3 {
293        return s.chars().take(limit).collect();
294    }
295    let head: String = s.chars().take(limit - 3).collect();
296    format!("{}...", head)
297}
298
299/// A value as a message names it: its JSON with the quotes stripped, clipped.
300pub fn value_to_string(v: &Value) -> String {
301    match v {
302        Value::Undefined => "undefined".to_string(),
303        Value::Null => "null".to_string(),
304        Value::Str(s) => truncate_text(&json_text(s).replace('"', ""), ERR_VALUE_LIMIT),
305        Value::Num(n) => js_number(*n),
306        Value::Bool(b) => b.to_string(),
307        Value::BigInt(b) => b.to_string(),
308        Value::Date(ms) => js_date_string(*ms),
309        Value::Func(_) => "function".to_string(),
310        Value::Arr(_) | Value::Obj(_) => {
311            truncate_text(&json_render(v).replace('"', ""), ERR_VALUE_LIMIT)
312        }
313    }
314}
315
316/// The kind a message calls a value: "value" for nothing, null or NaN,
317/// otherwise its `typeof`, with arrays told from objects.
318pub fn value_kind(v: &Value) -> &'static str {
319    match v {
320        Value::Undefined | Value::Null => "value",
321        Value::Num(n) if n.is_nan() => "value",
322        Value::Num(_) => "number",
323        Value::Str(_) => "string",
324        Value::Bool(_) => "boolean",
325        Value::BigInt(_) => "bigint",
326        Value::Arr(_) => "array",
327        Value::Obj(_) | Value::Date(_) => "object",
328        Value::Func(_) => "function",
329    }
330}
331
332impl fmt::Display for Value {
333    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
334        f.write_str(&value_to_string(self))
335    }
336}
337
338/// Whether a number is an integer as `Number.isInteger` judges it.
339pub fn is_integer(x: f64) -> bool {
340    x.is_finite() && x == x.trunc()
341}
342
343#[cfg(feature = "serde")]
344mod serde_conv {
345    use super::{Map, Value};
346
347    impl From<serde_json::Value> for Value {
348        fn from(v: serde_json::Value) -> Self {
349            match v {
350                serde_json::Value::Null => Value::Null,
351                serde_json::Value::Bool(b) => Value::Bool(b),
352                serde_json::Value::Number(n) => Value::Num(n.as_f64().unwrap_or(f64::NAN)),
353                serde_json::Value::String(s) => Value::Str(s),
354                serde_json::Value::Array(a) => Value::Arr(a.into_iter().map(Value::from).collect()),
355                serde_json::Value::Object(m) => {
356                    let mut out = Map::with_capacity(m.len());
357                    for (k, v) in m {
358                        out.insert(k, Value::from(v));
359                    }
360                    Value::Obj(out)
361                }
362            }
363        }
364    }
365
366    impl From<Value> for serde_json::Value {
367        /// As `JSON.stringify` would carry it: an undefined property is dropped,
368        /// an undefined element and a NaN become null, a date its ISO text.
369        fn from(v: Value) -> Self {
370            match v {
371                Value::Undefined | Value::Null => serde_json::Value::Null,
372                Value::Bool(b) => serde_json::Value::Bool(b),
373                // An integral number within the safe range is carried as an
374                // integer, so `1` compares equal to `json!(1)`.
375                Value::Num(n) if n.fract() == 0.0 && n.abs() < 9007199254740992.0 => {
376                    serde_json::Value::from(n as i64)
377                }
378                Value::Num(n) => serde_json::Number::from_f64(n)
379                    .map(serde_json::Value::Number)
380                    .unwrap_or(serde_json::Value::Null),
381                Value::Str(s) => serde_json::Value::String(s),
382                Value::BigInt(b) => serde_json::Value::String(b.to_string()),
383                Value::Date(ms) => serde_json::Value::String(super::js_date_string(ms)),
384                Value::Func(_) => serde_json::Value::Null,
385                Value::Arr(a) => {
386                    serde_json::Value::Array(a.into_iter().map(serde_json::Value::from).collect())
387                }
388                Value::Obj(m) => {
389                    let mut out = serde_json::Map::with_capacity(m.len());
390                    for (k, v) in m {
391                        if !v.is_undefined() {
392                            out.insert(k, serde_json::Value::from(v));
393                        }
394                    }
395                    serde_json::Value::Object(out)
396                }
397            }
398        }
399    }
400}
401
402#[cfg(test)]
403mod tests {
404    use super::*;
405
406    #[test]
407    fn numbers_print_as_javascript_does() {
408        let cases: [(f64, &str); 14] = [
409            (0.0, "0"),
410            (1.0, "1"),
411            (-1.0, "-1"),
412            (1.5, "1.5"),
413            (0.1, "0.1"),
414            (100.0, "100"),
415            (1e21, "1e+21"),
416            (1e20, "100000000000000000000"),
417            (123456789012345680000.0, "123456789012345680000"),
418            (1e-7, "1e-7"),
419            (0.000001, "0.000001"),
420            (1.5e-7, "1.5e-7"),
421            (1.2345e25, "1.2345e+25"),
422            (f64::NAN, "NaN"),
423        ];
424        for (x, want) in cases {
425            assert_eq!(js_number(x), want, "{}", x);
426        }
427        assert_eq!(js_number(f64::INFINITY), "Infinity");
428        assert_eq!(js_number(f64::NEG_INFINITY), "-Infinity");
429    }
430
431    #[test]
432    fn dates_print_as_iso() {
433        assert_eq!(js_date_string(0), "1970-01-01T00:00:00.000Z");
434        assert_eq!(
435            js_date_string(4_765_132_800_000),
436            "2121-01-01T00:00:00.000Z"
437        );
438        assert_eq!(js_date_string(-1), "1969-12-31T23:59:59.999Z");
439        assert_eq!(
440            js_date_string(-62_198_755_200_000),
441            "-000001-01-01T00:00:00.000Z"
442        );
443        assert_eq!(
444            js_date_string(253_402_300_800_000),
445            "+010000-01-01T00:00:00.000Z"
446        );
447    }
448
449    #[test]
450    fn strings_render_with_json_escapes_and_no_quotes() {
451        assert_eq!(json_text("a\"b\\c\n\u{1}"), "\"a\\\"b\\\\c\\n\\u0001\"");
452        assert_eq!(value_to_string(&Value::from("a\"b\\c")), "a\\b\\\\c");
453        let long = "x".repeat(200);
454        let t = value_to_string(&Value::from(long.as_str()));
455        assert_eq!(t.len(), ERR_VALUE_LIMIT);
456        assert!(t.ends_with("..."));
457        assert_eq!(truncate_text("abcdef", 2), "ab");
458    }
459
460    #[test]
461    fn containers_render_in_insertion_order() {
462        let mut m = Map::new();
463        m.insert("b".into(), Value::from(1));
464        m.insert(
465            "a".into(),
466            Value::Arr(vec![
467                Value::Undefined,
468                Value::from("x"),
469                Value::Num(f64::NAN),
470            ]),
471        );
472        m.insert("u".into(), Value::Undefined);
473        m.insert("d".into(), Value::Date(0));
474        assert_eq!(
475            value_to_string(&Value::Obj(m)),
476            "{b:1,a:[null,x,NaN],d:1970-01-01T00:00:00.000Z}"
477        );
478        assert_eq!(json_render(&Value::Num(f64::INFINITY)), "null");
479        assert_eq!(value_kind(&Value::Arr(vec![])), "array");
480        assert_eq!(value_kind(&Value::Num(f64::NAN)), "value");
481        assert_eq!(value_kind(&Value::Date(0)), "object");
482        assert_eq!(value_to_string(&Value::BigInt(BigInt::from(7))), "7");
483        assert_eq!(json_render(&Value::BigInt(BigInt::from(7))), "\"7\"");
484        assert_eq!(value_to_string(&Value::Func(1)), "function");
485        assert_eq!(value_to_string(&Value::Date(0)), "1970-01-01T00:00:00.000Z");
486        assert_eq!(json_text("\r\t"), "\"\\r\\t\"");
487        assert_eq!(json_render(&Value::Func(1)), "null");
488        assert_eq!(value_kind(&Value::Func(1)), "function");
489        assert_eq!(value_kind(&Value::BigInt(BigInt::from(1))), "bigint");
490        assert_eq!(Value::Func(1).type_of(), "function");
491        assert_eq!(Value::Null.type_of(), "object");
492        assert_eq!(Value::Undefined.type_of(), "undefined");
493        assert_eq!(Value::BigInt(BigInt::from(1)).type_of(), "bigint");
494        assert_eq!(Value::Date(0).type_of(), "object");
495        assert_eq!(format!("{}", Value::Null), "null");
496        assert_eq!(format!("{}", Value::Undefined), "undefined");
497        assert_eq!(format!("{}", Value::Bool(true)), "true");
498        assert!(is_integer(3.0) && !is_integer(3.5) && !is_integer(f64::NAN));
499    }
500
501    #[test]
502    fn conversions() {
503        assert_eq!(Value::from(true), Value::Bool(true));
504        assert_eq!(Value::from(2i64), Value::Num(2.0));
505        assert_eq!(Value::from(2i32), Value::Num(2.0));
506        assert_eq!(Value::from(2u32), Value::Num(2.0));
507        assert_eq!(Value::from(2usize), Value::Num(2.0));
508        assert_eq!(Value::from(String::from("s")), Value::Str("s".into()));
509        assert_eq!(
510            Value::from(vec![Value::Null]),
511            Value::Arr(vec![Value::Null])
512        );
513        assert_eq!(Value::from(Map::new()), Value::Obj(Map::new()));
514        assert_eq!(Value::from(BigInt::from(1)), Value::BigInt(BigInt::from(1)));
515        assert_eq!(Value::default(), Value::Undefined);
516        assert!(Value::Null.is_null());
517        assert_eq!(Value::from(1.5).as_f64(), Some(1.5));
518        assert_eq!(Value::from("x").as_str(), Some("x"));
519        assert!(Value::Null.as_str().is_none() && Value::Null.as_f64().is_none());
520        assert!(Value::Null.as_obj().is_none() && Value::Null.as_arr().is_none());
521        assert!(Value::Obj(Map::new()).as_obj().is_some());
522        assert!(Value::Arr(vec![]).as_arr().is_some());
523    }
524
525    #[cfg(feature = "serde")]
526    #[test]
527    fn serde_round_trip() {
528        let j: serde_json::Value =
529            serde_json::from_str(r#"{"b":1,"a":[null,"x",true],"c":{"z":1.5}}"#).unwrap();
530        let v = Value::from(j.clone());
531        let keys: Vec<&String> = v.as_obj().unwrap().keys().collect();
532        assert_eq!(keys, vec!["b", "a", "c"]);
533        assert_eq!(serde_json::Value::from(v), j);
534        let mut m = Map::new();
535        m.insert("u".into(), Value::Undefined);
536        m.insert("n".into(), Value::Num(f64::NAN));
537        m.insert("d".into(), Value::Date(0));
538        m.insert("b".into(), Value::BigInt(BigInt::from(3)));
539        m.insert("f".into(), Value::Func(0));
540        m.insert("e".into(), Value::Arr(vec![Value::Undefined]));
541        let out = serde_json::Value::from(Value::Obj(m)).to_string();
542        assert_eq!(
543            out,
544            r#"{"n":null,"d":"1970-01-01T00:00:00.000Z","b":"3","f":null,"e":[null]}"#
545        );
546    }
547
548    #[test]
549    fn remaining_kinds_render() {
550        let m = Map::new();
551        let cases: Vec<(Value, &str, &str)> = vec![
552            (Value::Num(1.0), "number", "number"),
553            (Value::Str("s".into()), "string", "string"),
554            (Value::BigInt(BigInt::from(2)), "bigint", "bigint"),
555            (Value::Arr(vec![]), "object", "array"),
556            (Value::Obj(m), "object", "object"),
557            (Value::Date(0), "object", "object"),
558            (Value::Func(1), "function", "function"),
559            (Value::Bool(true), "boolean", "boolean"),
560            (Value::Null, "object", "value"),
561            (Value::Undefined, "undefined", "value"),
562            (Value::Num(f64::NAN), "number", "value"),
563        ];
564        for (v, ty, kind) in cases {
565            assert_eq!(v.type_of(), ty, "{:?}", v);
566            assert_eq!(value_kind(&v), kind, "{:?}", v);
567        }
568        assert_eq!(value_to_string(&Value::Func(1)), "function");
569        assert_eq!(value_to_string(&Value::Date(0)), "1970-01-01T00:00:00.000Z");
570        assert_eq!(json_text("\r\t"), "\"\\r\\t\"");
571        assert_eq!(json_text("a\u{8}b\u{c}c\u{1}d"), "\"a\\bb\\fc\\u0001d\"");
572    }
573}