1#[cfg(feature = "bigint")]
2pub(crate) mod bigint;
3
4use std::convert::{TryFrom, TryInto};
5use std::{collections::HashMap, error, fmt};
6
7#[cfg(feature = "bigint")]
8pub use bigint::BigInt;
9
10#[derive(PartialEq, Clone, Debug)]
12#[allow(missing_docs)]
13pub enum JsValue {
14 Undefined,
15 Null,
16 Bool(bool),
17 Int(i32),
18 Float(f64),
19 String(String),
20 Array(Vec<JsValue>),
21 Object(HashMap<String, JsValue>),
22 #[cfg(feature = "chrono")]
25 Date(chrono::DateTime<chrono::Utc>),
26 #[cfg(feature = "bigint")]
29 BigInt(crate::BigInt),
30 #[doc(hidden)]
31 __NonExhaustive,
32}
33
34impl JsValue {
35 pub fn as_str(&self) -> Option<&str> {
39 match self {
40 JsValue::String(ref s) => Some(s.as_str()),
41 _ => None,
42 }
43 }
44
45 pub fn into_string(self) -> Option<String> {
47 match self {
48 JsValue::String(s) => Some(s),
49 _ => None,
50 }
51 }
52}
53
54macro_rules! value_impl_from {
55 (
56 (
57 $( $t1:ty => $var1:ident, )*
58 )
59 (
60 $( $t2:ty => |$exprname:ident| $expr:expr => $var2:ident, )*
61 )
62 ) => {
63 $(
64 impl From<$t1> for JsValue {
65 fn from(value: $t1) -> Self {
66 JsValue::$var1(value)
67 }
68 }
69
70 impl std::convert::TryFrom<JsValue> for $t1 {
71 type Error = ValueError;
72
73 fn try_from(value: JsValue) -> Result<Self, Self::Error> {
74 match value {
75 JsValue::$var1(inner) => Ok(inner),
76 _ => Err(ValueError::UnexpectedType)
77 }
78
79 }
80 }
81 )*
82 $(
83 impl From<$t2> for JsValue {
84 fn from(value: $t2) -> Self {
85 let $exprname = value;
86 let inner = $expr;
87 JsValue::$var2(inner)
88 }
89 }
90 )*
91 }
92}
93
94value_impl_from! {
95 (
96 bool => Bool,
97 i32 => Int,
98 f64 => Float,
99 String => String,
100 )
101 (
102 i8 => |x| i32::from(x) => Int,
103 i16 => |x| i32::from(x) => Int,
104 u8 => |x| i32::from(x) => Int,
105 u16 => |x| i32::from(x) => Int,
106 u32 => |x| f64::from(x) => Float,
107 )
108}
109
110#[cfg(feature = "bigint")]
111value_impl_from! {
112 ()
113 (
114 i64 => |x| x.into() => BigInt,
115 u64 => |x| num_bigint::BigInt::from(x).into() => BigInt,
116 i128 => |x| num_bigint::BigInt::from(x).into() => BigInt,
117 u128 => |x| num_bigint::BigInt::from(x).into() => BigInt,
118 num_bigint::BigInt => |x| x.into() => BigInt,
119 )
120}
121
122#[cfg(feature = "bigint")]
123impl std::convert::TryFrom<JsValue> for i64 {
124 type Error = ValueError;
125
126 fn try_from(value: JsValue) -> Result<Self, Self::Error> {
127 match value {
128 JsValue::Int(int) => Ok(int as i64),
129 JsValue::BigInt(bigint) => bigint.as_i64().ok_or(ValueError::UnexpectedType),
130 _ => Err(ValueError::UnexpectedType),
131 }
132 }
133}
134
135#[cfg(feature = "bigint")]
136macro_rules! value_bigint_impl_tryfrom {
137 (
138 ($($t:ty => $to_type:ident, )*)
139 ) => {
140 $(
141 impl std::convert::TryFrom<JsValue> for $t {
142 type Error = ValueError;
143
144 fn try_from(value: JsValue) -> Result<Self, Self::Error> {
145 use num_traits::ToPrimitive;
146
147 match value {
148 JsValue::Int(int) => Ok(int as $t),
149 JsValue::BigInt(bigint) => bigint
150 .into_bigint()
151 .$to_type()
152 .ok_or(ValueError::UnexpectedType),
153 _ => Err(ValueError::UnexpectedType),
154 }
155 }
156 }
157 )*
158 }
159}
160
161#[cfg(feature = "bigint")]
162value_bigint_impl_tryfrom! {
163 (
164 u64 => to_u64,
165 i128 => to_i128,
166 u128 => to_u128,
167 )
168}
169
170#[cfg(feature = "bigint")]
171impl std::convert::TryFrom<JsValue> for num_bigint::BigInt {
172 type Error = ValueError;
173
174 fn try_from(value: JsValue) -> Result<Self, Self::Error> {
175 match value {
176 JsValue::Int(int) => Ok(num_bigint::BigInt::from(int)),
177 JsValue::BigInt(bigint) => Ok(bigint.into_bigint()),
178 _ => Err(ValueError::UnexpectedType),
179 }
180 }
181}
182
183impl<T> From<Vec<T>> for JsValue
184where
185 T: Into<JsValue>,
186{
187 fn from(values: Vec<T>) -> Self {
188 let items = values.into_iter().map(|x| x.into()).collect();
189 JsValue::Array(items)
190 }
191}
192
193impl<T> TryFrom<JsValue> for Vec<T>
194where
195 T: TryFrom<JsValue>,
196{
197 type Error = ValueError;
198
199 fn try_from(value: JsValue) -> Result<Self, Self::Error> {
200 match value {
201 JsValue::Array(items) => items
202 .into_iter()
203 .map(|item| item.try_into().map_err(|_| ValueError::UnexpectedType))
204 .collect(),
205 _ => Err(ValueError::UnexpectedType),
206 }
207 }
208}
209
210impl<'a> From<&'a str> for JsValue {
211 fn from(val: &'a str) -> Self {
212 JsValue::String(val.into())
213 }
214}
215
216impl<T> From<Option<T>> for JsValue
217where
218 T: Into<JsValue>,
219{
220 fn from(opt: Option<T>) -> Self {
221 if let Some(value) = opt {
222 value.into()
223 } else {
224 JsValue::Null
225 }
226 }
227}
228
229impl<K, V> From<HashMap<K, V>> for JsValue
230where
231 K: Into<String>,
232 V: Into<JsValue>,
233{
234 fn from(map: HashMap<K, V>) -> Self {
235 let new_map = map.into_iter().map(|(k, v)| (k.into(), v.into())).collect();
236 JsValue::Object(new_map)
237 }
238}
239
240impl<V> TryFrom<JsValue> for HashMap<String, V>
241where
242 V: TryFrom<JsValue>,
243{
244 type Error = ValueError;
245
246 fn try_from(value: JsValue) -> Result<Self, Self::Error> {
247 match value {
248 JsValue::Object(object) => object
249 .into_iter()
250 .map(|(k, v)| match v.try_into() {
251 Ok(v) => Ok((k, v)),
252 Err(_) => Err(ValueError::UnexpectedType),
253 })
254 .collect(),
255 _ => Err(ValueError::UnexpectedType),
256 }
257 }
258}
259
260#[derive(PartialEq, Eq, Debug)]
262#[non_exhaustive]
263pub enum ValueError {
264 InvalidString(std::str::Utf8Error),
266 StringWithZeroBytes(std::ffi::NulError),
268 Internal(String),
270 UnexpectedType,
272}
273
274impl From<std::convert::Infallible> for ValueError {
277 fn from(_: std::convert::Infallible) -> Self {
278 unreachable!()
279 }
280}
281
282impl fmt::Display for ValueError {
283 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
284 use ValueError::*;
285 match self {
286 InvalidString(e) => write!(f, "Value conversion failed - invalid non-utf8 string: {e}"),
287 StringWithZeroBytes(_) => write!(f, "String contains \\0 bytes",),
288 Internal(e) => write!(f, "Value conversion failed - internal error: {e}"),
289 UnexpectedType => write!(f, "Could not convert - received unexpected type"),
290 }
291 }
292}
293
294impl error::Error for ValueError {}
295
296#[cfg(test)]
297mod tests {
298 #[allow(unused_imports)]
299 use super::*;
300
301 #[cfg(feature = "bigint")]
302 #[test]
303 fn test_bigint_from_i64() {
304 let int = 1234i64;
305 let value = JsValue::from(int);
306 if let JsValue::BigInt(value) = value {
307 assert_eq!(value.as_i64(), Some(int));
308 } else {
309 panic!("Expected JsValue::BigInt");
310 }
311 }
312
313 #[cfg(feature = "bigint")]
314 #[test]
315 fn test_bigint_from_bigint() {
316 let bigint = num_bigint::BigInt::from(std::i128::MAX);
317 let value = JsValue::from(bigint.clone());
318 if let JsValue::BigInt(value) = value {
319 assert_eq!(value.into_bigint(), bigint);
320 } else {
321 panic!("Expected JsValue::BigInt");
322 }
323 }
324
325 #[cfg(feature = "bigint")]
326 #[test]
327 fn test_bigint_i64_bigint_eq() {
328 let value_i64 = JsValue::BigInt(1234i64.into());
329 let value_bigint = JsValue::BigInt(num_bigint::BigInt::from(1234i64).into());
330 assert_eq!(value_i64, value_bigint);
331 }
332}