use std::{fmt, str::FromStr};
use crate::{object::JsObject, Context, JsNativeError, JsResult, JsString, JsValue};
pub(crate) trait OptionType: Sized {
fn from_value(value: JsValue, context: &mut Context) -> JsResult<Self>;
}
pub(crate) trait ParsableOptionType: FromStr {}
impl<T: ParsableOptionType> OptionType for T
where
T::Err: fmt::Display,
{
fn from_value(value: JsValue, context: &mut Context) -> JsResult<Self> {
value
.to_string(context)?
.to_std_string_escaped()
.parse::<Self>()
.map_err(|err| JsNativeError::range().with_message(err.to_string()).into())
}
}
pub(crate) fn get_option<T: OptionType>(
options: &JsObject,
property: JsString,
context: &mut Context,
) -> JsResult<Option<T>> {
let value = options.get(property, context)?;
if value.is_undefined() {
return Ok(None);
}
T::from_value(value, context).map(Some)
}
pub(crate) fn get_options_object(options: &JsValue) -> JsResult<JsObject> {
match options {
JsValue::Undefined => {
Ok(JsObject::with_null_proto())
}
JsValue::Object(obj) => {
Ok(obj.clone())
}
_ => Err(JsNativeError::typ()
.with_message("GetOptionsObject: provided options is not an object")
.into()),
}
}
impl OptionType for bool {
fn from_value(value: JsValue, _: &mut Context) -> JsResult<Self> {
Ok(value.to_boolean())
}
}
impl OptionType for JsString {
fn from_value(value: JsValue, context: &mut Context) -> JsResult<Self> {
value.to_string(context)
}
}
impl OptionType for f64 {
fn from_value(value: JsValue, context: &mut Context) -> JsResult<Self> {
let value = value.to_number(context)?;
if !value.is_finite() {
return Err(JsNativeError::range()
.with_message("numeric option must be finite.")
.into());
}
Ok(value)
}
}
#[derive(Debug, Copy, Clone, Default)]
pub(crate) enum RoundingMode {
Ceil,
Floor,
Expand,
Trunc,
HalfCeil,
HalfFloor,
#[default]
HalfExpand,
HalfTrunc,
HalfEven,
}
impl RoundingMode {
#[cfg(feature = "intl")]
pub(crate) fn to_js_string(self) -> JsString {
use crate::js_string;
match self {
Self::Ceil => js_string!("ceil"),
Self::Floor => js_string!("floor"),
Self::Expand => js_string!("expand"),
Self::Trunc => js_string!("trunc"),
Self::HalfCeil => js_string!("halfCeil"),
Self::HalfFloor => js_string!("halfFloor"),
Self::HalfExpand => js_string!("halfExpand"),
Self::HalfTrunc => js_string!("halfTrunc"),
Self::HalfEven => js_string!("halfEven"),
}
}
}
#[derive(Debug)]
pub(crate) struct ParseRoundingModeError;
impl fmt::Display for ParseRoundingModeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("provided string was not a valid rounding mode")
}
}
impl FromStr for RoundingMode {
type Err = ParseRoundingModeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"ceil" => Ok(Self::Ceil),
"floor" => Ok(Self::Floor),
"expand" => Ok(Self::Expand),
"trunc" => Ok(Self::Trunc),
"halfCeil" => Ok(Self::HalfCeil),
"halfFloor" => Ok(Self::HalfFloor),
"halfExpand" => Ok(Self::HalfExpand),
"halfTrunc" => Ok(Self::HalfTrunc),
"halfEven" => Ok(Self::HalfEven),
_ => Err(ParseRoundingModeError),
}
}
}
impl ParsableOptionType for RoundingMode {}
#[cfg(feature = "temporal")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum UnsignedRoundingMode {
Infinity,
Zero,
HalfInfinity,
HalfZero,
HalfEven,
}
impl RoundingMode {
#[cfg(feature = "temporal")]
#[allow(dead_code)]
pub(crate) const fn negate(self) -> Self {
use RoundingMode::{
Ceil, Expand, Floor, HalfCeil, HalfEven, HalfExpand, HalfFloor, HalfTrunc, Trunc,
};
match self {
Ceil => Self::Floor,
Floor => Self::Ceil,
HalfCeil => Self::HalfFloor,
HalfFloor => Self::HalfCeil,
Trunc => Self::Trunc,
Expand => Self::Expand,
HalfTrunc => Self::HalfTrunc,
HalfExpand => Self::HalfExpand,
HalfEven => Self::HalfEven,
}
}
#[cfg(feature = "temporal")]
#[allow(dead_code)]
pub(crate) const fn get_unsigned_round_mode(self, is_negative: bool) -> UnsignedRoundingMode {
use RoundingMode::{
Ceil, Expand, Floor, HalfCeil, HalfEven, HalfExpand, HalfFloor, HalfTrunc, Trunc,
};
match self {
Ceil if !is_negative => UnsignedRoundingMode::Infinity,
Ceil => UnsignedRoundingMode::Zero,
Floor if !is_negative => UnsignedRoundingMode::Zero,
Floor | Trunc | Expand => UnsignedRoundingMode::Infinity,
HalfCeil if !is_negative => UnsignedRoundingMode::HalfInfinity,
HalfCeil | HalfTrunc => UnsignedRoundingMode::HalfZero,
HalfFloor if !is_negative => UnsignedRoundingMode::HalfZero,
HalfFloor | HalfExpand => UnsignedRoundingMode::HalfInfinity,
HalfEven => UnsignedRoundingMode::HalfEven,
}
}
}