//! Types and functions for applying JavaScript Convert rules to [`JsValue`] when //! converting. See (Section 9) for //! conversion rules of JavaScript types. //! //! Some conversions are not specified in the spec (e.g. integer conversions), //! and we apply rules that make sense (e.g. converting to Number and rounding //! if necessary). use boa_engine::JsNativeError; use crate::value::TryFromJs; use crate::{Context, JsResult, JsString, JsValue}; /// A wrapper type that allows converting a `JsValue` to a specific type. /// This is useful when you want to convert a `JsValue` to a Rust type. /// /// # Example /// Convert a string to number. /// ``` /// # use boa_engine::{Context, js_string, JsValue}; /// # use boa_engine::value::{Convert, TryFromJs}; /// # let mut context = Context::default(); /// let value = JsValue::from(js_string!("42")); /// let Convert(converted): Convert = Convert::try_from_js(&value, &mut context).unwrap(); /// /// assert_eq!(converted, 42); /// ``` /// /// Convert a number to a bool. /// ``` /// # use boa_engine::{Context, js_string, JsValue}; /// # use boa_engine::value::{Convert, TryFromJs}; /// # let mut context = Context::default(); /// let Convert(conv0): Convert = Convert::try_from_js(&JsValue::Integer(0), &mut context).unwrap(); /// let Convert(conv5): Convert = Convert::try_from_js(&JsValue::Integer(5), &mut context).unwrap(); /// let Convert(conv_nan): Convert = Convert::try_from_js(&JsValue::Rational(f64::NAN), &mut context).unwrap(); /// /// assert_eq!(conv0, false); /// assert_eq!(conv5, true); /// assert_eq!(conv_nan, false); /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Convert(pub T); impl From for Convert { fn from(value: T) -> Self { Self(value) } } macro_rules! decl_convert_to_int { ($($ty:ty),*) => { $( impl TryFromJs for Convert<$ty> { fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult { 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()) // Only round if it differs from the next integer by an epsilon } 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 { 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 { fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult { value .to_string(context) .and_then(|s| s.to_std_string().map_err(|_| JsNativeError::typ().into())) .map(Convert) } } impl TryFromJs for Convert { fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult { value.to_string(context).map(Convert) } } impl TryFromJs for Convert { fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult { Ok(Self(value.to_boolean())) } }