use std::str::FromStr;
use temporal_rs::options::{RoundingIncrement, RoundingMode, Unit};
use crate::{
ecmascript::{
Agent, BUILTIN_STRING_MEMORY, ExceptionType, JsResult, Object, PropertyKey, String, Value,
get, temporal_err_to_js_err, to_integer_with_truncation, to_string,
},
engine::{Bindable, GcScope, NoGcScope},
};
pub(crate) trait OptionType: Sized {
fn from_value<'gc>(
agent: &mut Agent,
value: Value,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Self>;
}
pub(crate) trait StringOptionType: Sized {
fn from_string<'gc>(
agent: &mut Agent,
value: String,
gc: NoGcScope<'gc, '_>,
) -> JsResult<'gc, Self>;
}
impl<T: StringOptionType> OptionType for T {
fn from_value<'gc>(
agent: &mut Agent,
value: Value,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Self> {
let value = to_string(agent, value, gc.reborrow())
.unbind()?
.bind(gc.nogc());
Self::from_string(agent, value.unbind(), gc.into_nogc())
}
}
impl StringOptionType for RoundingMode {
fn from_string<'gc>(
agent: &mut Agent,
value: String,
gc: NoGcScope<'gc, '_>,
) -> JsResult<'gc, Self> {
let value = value.as_str(agent).unwrap_or("");
Self::from_str(value).map_err(|err| temporal_err_to_js_err(agent, err, gc.into_nogc()))
}
}
impl StringOptionType for Unit {
fn from_string<'gc>(
agent: &mut Agent,
value: String,
gc: NoGcScope<'gc, '_>,
) -> JsResult<'gc, Self> {
let value = value.as_str(agent).unwrap_or("");
Self::from_str(value).map_err(|err| {
agent.throw_exception(ExceptionType::RangeError, format!("{err}"), gc.into_nogc())
})
}
}
pub(crate) fn get_options_object<'gc>(
agent: &mut Agent,
options: Value,
gc: NoGcScope<'gc, '_>,
) -> JsResult<'gc, Option<Object<'gc>>> {
let options = options.bind(gc);
if options.is_undefined() {
Ok(None)
} else if let Ok(options) = Object::try_from(options) {
Ok(Some(options))
} else {
Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"options provided to GetOptionsObject is not an object",
gc,
))
}
}
pub(crate) fn get_option<'gc, T>(
agent: &mut Agent,
options: Object,
property: PropertyKey,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Option<T>>
where
T: OptionType,
{
let options = options.bind(gc.nogc());
let property = property.bind(gc.nogc());
let value = get(agent, options.unbind(), property.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc());
if value.is_undefined() {
return Ok(None);
}
T::from_value(agent, value.unbind(), gc).map(Some)
}
pub(crate) fn get_rounding_mode_option<'gc>(
agent: &mut Agent,
options: Object,
fallback: RoundingMode,
gc: GcScope<'gc, '_>,
) -> JsResult<'gc, RoundingMode> {
let options = options.bind(gc.nogc());
match get_option::<RoundingMode>(
agent,
options.unbind(),
BUILTIN_STRING_MEMORY.roundingMode.into(),
gc,
)? {
Some(mode) => Ok(mode),
None => Ok(fallback),
}
}
pub(crate) fn get_rounding_increment_option<'gc>(
agent: &mut Agent,
options: Object,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, RoundingIncrement> {
let options = options.bind(gc.nogc());
let value = get(
agent,
options.unbind(),
BUILTIN_STRING_MEMORY.roundingIncrement.into(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
if value.is_undefined() {
return Ok(RoundingIncrement::default());
}
let integer_increment =
to_integer_with_truncation(agent, value.unbind(), gc.reborrow()).unbind()?;
RoundingIncrement::try_new(u32::try_from(integer_increment).unwrap_or(u32::MAX))
.map_err(|err| temporal_err_to_js_err(agent, err, gc.into_nogc()))
}