Skip to main content

zen_types/variable/
conv.rs

1use crate::rccell::RcCell;
2use crate::variable::Variable;
3use rust_decimal::Decimal;
4use rust_decimal::prelude::FromPrimitive;
5use serde_json::{Number, Value};
6#[cfg(not(feature = "arbitrary_precision"))]
7use std::str::FromStr;
8use thiserror::Error;
9
10#[derive(Debug, Error)]
11pub enum VariableConversionError {
12    #[error("number out of range: {0}")]
13    NumberOutOfRange(String),
14}
15
16impl Variable {
17    fn decimal_from_number(n: &Number) -> Option<Decimal> {
18        #[cfg(feature = "arbitrary_precision")]
19        {
20            Decimal::from_str_exact(n.as_str())
21                .or_else(|_| Decimal::from_scientific(n.as_str()))
22                .ok()
23                .or_else(|| n.as_f64().and_then(Decimal::from_f64))
24        }
25
26        #[cfg(not(feature = "arbitrary_precision"))]
27        {
28            if let Some(u) = n.as_u64() {
29                return Some(u.into());
30            }
31            if let Some(i) = n.as_i64() {
32                return Some(i.into());
33            }
34            n.as_f64().and_then(Decimal::from_f64)
35        }
36    }
37}
38
39impl Variable {
40    pub fn try_from_value(value: Value) -> Result<Self, VariableConversionError> {
41        match value {
42            Value::Null => Ok(Variable::Null),
43            Value::Bool(b) => Ok(Variable::Bool(b)),
44            Value::Number(n) => Self::decimal_from_number(&n)
45                .map(Variable::Number)
46                .ok_or_else(|| VariableConversionError::NumberOutOfRange(n.to_string())),
47            Value::String(s) => Ok(Variable::String((s.as_str()).into())),
48            Value::Array(arr) => Ok(Variable::from_array(
49                arr.into_iter()
50                    .map(Variable::try_from_value)
51                    .collect::<Result<_, _>>()?,
52            )),
53            Value::Object(obj) => Ok(Variable::from_object(
54                obj.into_iter()
55                    .map(|(k, v)| {
56                        Ok((
57                            crate::symbol::Symbol::from(k.as_str()),
58                            Variable::try_from_value(v)?,
59                        ))
60                    })
61                    .collect::<Result<_, VariableConversionError>>()?,
62            )),
63        }
64    }
65}
66
67impl From<Value> for Variable {
68    fn from(value: Value) -> Self {
69        match value {
70            Value::Number(n) => Variable::decimal_from_number(&n)
71                .map(Variable::Number)
72                .unwrap_or(Variable::Null),
73            Value::Array(arr) => {
74                Variable::from_array(arr.into_iter().map(Variable::from).collect())
75            }
76            Value::Object(obj) => Variable::from_object(
77                obj.into_iter()
78                    .map(|(k, v)| (crate::symbol::Symbol::from(k.as_str()), Variable::from(v)))
79                    .collect(),
80            ),
81            other => Variable::from(&other),
82        }
83    }
84}
85
86impl From<&Value> for Variable {
87    fn from(value: &Value) -> Self {
88        match value {
89            Value::Null => Variable::Null,
90            Value::Bool(b) => Variable::Bool(*b),
91            Value::Number(n) => Variable::decimal_from_number(n)
92                .map(Variable::Number)
93                .unwrap_or(Variable::Null),
94            Value::String(s) => Variable::String((s.as_str()).into()),
95            Value::Array(arr) => Variable::from_array(arr.iter().map(Variable::from).collect()),
96            Value::Object(obj) => Variable::from_object(
97                obj.iter()
98                    .map(|(k, v)| (crate::symbol::Symbol::from(k.as_str()), Variable::from(v)))
99                    .collect(),
100            ),
101        }
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108    use serde_json::json;
109
110    #[test]
111    fn out_of_range_number_converts_to_null() {
112        assert_eq!(Variable::from(json!(1e40)), Variable::Null);
113        assert_eq!(Variable::from(json!(-1e40)), Variable::Null);
114        assert_eq!(
115            Variable::from(json!({ "nested": [1e40] })),
116            Variable::from(json!({ "nested": [null] }))
117        );
118    }
119
120    #[test]
121    fn underflowing_number_rounds_to_zero() {
122        assert_eq!(
123            Variable::from(json!(1e-32)),
124            Variable::Number(Decimal::ZERO)
125        );
126    }
127
128    #[test]
129    fn try_from_value_rejects_out_of_range_numbers() {
130        assert!(matches!(
131            Variable::try_from_value(json!(1e40)),
132            Err(VariableConversionError::NumberOutOfRange(_))
133        ));
134        assert!(matches!(
135            Variable::try_from_value(json!({ "a": { "b": [1, 2, 1e40] } })),
136            Err(VariableConversionError::NumberOutOfRange(_))
137        ));
138    }
139
140    #[test]
141    fn try_from_value_accepts_regular_payloads() {
142        let converted =
143            Variable::try_from_value(json!({ "a": [1, 2.5, -3], "b": "x", "c": null, "d": true }))
144                .unwrap();
145        assert_eq!(
146            converted,
147            Variable::from(json!({ "a": [1, 2.5, -3], "b": "x", "c": null, "d": true }))
148        );
149    }
150}
151
152impl From<Variable> for Value {
153    fn from(value: Variable) -> Self {
154        match value {
155            Variable::Null => Value::Null,
156            Variable::Bool(b) => Value::Bool(b),
157            Variable::Number(n) => {
158                #[cfg(feature = "arbitrary_precision")]
159                {
160                    Value::Number(Number::from_string_unchecked(n.normalize().to_string()))
161                }
162                #[cfg(not(feature = "arbitrary_precision"))]
163                {
164                    Value::Number(
165                        Number::from_str(n.normalize().to_string().as_str())
166                            .expect("Allowed number"),
167                    )
168                }
169            }
170            Variable::String(s) => Value::String(s.to_string()),
171            Variable::Array(arr) => {
172                let vec = RcCell::try_unwrap(arr)
173                    .map(|cell| cell.into_inner())
174                    .unwrap_or_else(|s| {
175                        let borrowed = s.borrow();
176                        borrowed.clone()
177                    });
178
179                Value::Array(vec.into_iter().map(Value::from).collect())
180            }
181            Variable::Object(obj) => {
182                let hmap = RcCell::try_unwrap(obj)
183                    .map(|cell| cell.into_inner())
184                    .unwrap_or_else(|s| {
185                        let borrowed = s.borrow();
186                        borrowed.clone()
187                    });
188
189                Value::Object(
190                    hmap.into_iter()
191                        .map(|(k, v)| (k.to_string(), Value::from(v)))
192                        .collect(),
193                )
194            }
195            Variable::Dynamic(d) => d.to_value(),
196        }
197    }
198}