use chrono::{TimeZone, Utc};
use js::prelude::This;
use js::{Coerced, Ctx, Error, Exception, FromAtom, FromJs};
use rust_decimal::Decimal;
use super::classes;
use crate::val::{Array, Bytes, Datetime, Geometry, Object, Value};
impl<'js> FromJs<'js> for Value {
fn from_js(ctx: &Ctx<'js>, val: js::Value<'js>) -> Result<Self, Error> {
match val.type_of() {
js::Type::Undefined => Ok(Value::None),
js::Type::Null => Ok(Value::Null),
js::Type::Bool => Ok(Value::from(val.as_bool().expect("type checked as bool"))),
js::Type::Int => Ok(Value::from(val.as_int().expect("type checked as int") as f64)),
js::Type::Float => Ok(Value::from(val.as_float().expect("type checked as float"))),
js::Type::String => {
Ok(Value::from(val.into_string().expect("type checked as string").to_string()?))
}
js::Type::Array => {
let v = val.as_array().expect("type checked as array");
let mut x = Array::with_capacity(v.len());
for i in v.iter() {
let v = i?;
x.push(Value::from_js(ctx, v)?);
}
Ok(x.into())
}
js::Type::BigInt => {
let str = Coerced::<String>::from_js(ctx, val)?;
if let Ok(i) = str.parse::<i64>() {
return Ok(Value::from(i));
}
match str.parse::<Decimal>() {
Ok(x) => Ok(Value::from(x)),
Err(e) => Err(Exception::from_message(ctx.clone(), &e.to_string())?.throw()),
}
}
js::Type::Object | js::Type::Exception => {
let v = val.into_object().expect("type checked as object");
if v.is_error() {
let e: String = v.get(js::atom::PredefinedAtom::Message)?;
let (Ok(e) | Err(e)) =
Exception::from_message(ctx.clone(), &e).map(|x| x.throw());
return Err(e);
}
if let Some(v) = v.as_class::<classes::record::Record>() {
let borrow = v.borrow();
let v: &classes::record::Record = &borrow;
return Ok(v.value.clone().into());
}
if let Some(v) = v.as_class::<classes::duration::Duration>() {
let borrow = v.borrow();
let v: &classes::duration::Duration = &borrow;
return match &v.value {
Some(v) => Ok((*v).into()),
None => Ok(Value::None),
};
}
if let Some(v) = v.as_class::<classes::uuid::Uuid>() {
let borrow = v.borrow();
let v: &classes::uuid::Uuid = &borrow;
return match &v.value {
Some(v) => Ok((*v).into()),
None => Ok(Value::None),
};
}
if let Some(v) = v.as_class::<classes::file::File>() {
let borrow = v.borrow();
let v: &classes::file::File = &borrow;
return Ok(v.value.clone().into());
}
if let Some(v) = v.as_typed_array::<u8>() {
let Some(data) = v.as_bytes() else {
return Err(Error::new_from_js_message(
"Uint8Array",
"Bytes",
"Uint8Array data was consumed.",
));
};
return Ok(Value::Bytes(Bytes::from(data.to_vec())));
}
let date: js::Object = ctx.globals().get(js::atom::PredefinedAtom::Date)?;
if (v).is_instance_of(&date) {
let f: js::Function = v.get("getTime")?;
let m: i64 = f.call((This(v),))?;
let d = Utc
.timestamp_millis_opt(m)
.single()
.ok_or_else(|| Error::new_from_js("Date", "invalid timestamp"))?;
return Ok(Datetime::from(d).into());
}
if let Some(v) = v.as_array() {
let mut x = Array::with_capacity(v.len());
for i in v.iter() {
let v = i?;
let v = Value::from_js(ctx, v)?;
x.push(v);
}
return Ok(x.into());
}
if v.as_function().is_some() {
return Ok(Value::None);
}
let mut x = Object::default();
for i in v.props() {
let (k, v) = i?;
let k = String::from_atom(k)?;
let v = Value::from_js(ctx, v)?;
x.insert(k, v);
}
if let Some(x) = Geometry::try_from_object(&x) {
return Ok(x.into());
}
Ok(x.into())
}
_ => Ok(Value::Null),
}
}
}