use boa_engine::JsNativeError;
use boa_gc::{Finalize, Trace};
use crate::value::TryFromJs;
use crate::{Context, JsData, JsResult, JsString, JsValue};
#[derive(Debug, Clone, PartialEq, Eq, Trace, Finalize, JsData)]
pub struct Convert<T: TryFromJs>(pub T);
impl<T: TryFromJs> From<T> for Convert<T> {
fn from(value: T) -> Self {
Self(value)
}
}
impl<T: TryFromJs> AsRef<T> for Convert<T> {
fn as_ref(&self) -> &T {
&self.0
}
}
macro_rules! decl_convert_to_int {
($($ty:ty),*) => {
$(
impl TryFromJs for Convert<$ty> {
fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult<Self> {
value.to_numeric_number(context).and_then(|num| {
if num.is_finite() {
if num >= f64::from(<$ty>::MAX) {
Err(JsNativeError::typ()
.with_message("cannot convert value to integer, it is too large")
.into())
} else if num <= f64::from(<$ty>::MIN) {
Err(JsNativeError::typ()
.with_message("cannot convert value to integer, it is too small")
.into())
} else if num.abs().fract() >= (1.0 - f64::EPSILON) {
Ok(Convert(num.round() as $ty))
} else {
Ok(Convert(num as $ty))
}
} else if num.is_nan() {
Err(JsNativeError::typ()
.with_message("cannot convert NaN to integer")
.into())
} else if num.is_infinite() {
Err(JsNativeError::typ()
.with_message("cannot convert Infinity to integer")
.into())
} else {
Err(JsNativeError::typ()
.with_message("cannot convert non-finite number to integer")
.into())
}
})
}
}
)*
};
}
decl_convert_to_int!(i8, i16, i32, u8, u16, u32);
macro_rules! decl_convert_to_float {
($($ty:ty),*) => {
$(
impl TryFromJs for Convert<$ty> {
fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult<Self> {
value.to_numeric_number(context).and_then(|num| Ok(Convert(<$ty>::try_from(num).map_err(|_| {
JsNativeError::typ()
.with_message("cannot convert value to float")
})?)))
}
}
)*
};
}
decl_convert_to_float!(f64);
impl TryFromJs for Convert<String> {
fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult<Self> {
value
.to_string(context)
.and_then(|s| s.to_std_string().map_err(|_| JsNativeError::typ().into()))
.map(Convert)
}
}
impl TryFromJs for Convert<JsString> {
fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult<Self> {
value.to_string(context).map(Convert)
}
}
impl TryFromJs for Convert<bool> {
fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult<Self> {
Ok(Self(value.to_boolean()))
}
}