zen_types/variable/
conv.rs1use 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}