use rquickjs::object::Property;
use rquickjs::{Ctx, Object, Value};
use serde::Serialize;
use serde::de::DeserializeOwned;
pub use ferrijs_std::node::throw_named;
pub fn throw_named_with_stack(ctx: &Ctx<'_>, name: &str, message: String, stack: Option<String>) -> rquickjs::Error {
let Some(stack) = stack else {
return throw_named(ctx, name, message);
};
let built: rquickjs::Result<Value<'_>> = (|| {
let ctor: rquickjs::function::Constructor<'_> = ctx.globals().get("Error")?;
let err: Object<'_> = ctor.construct((message.as_str(),))?;
err.set("name", name)?;
err.set("stack", stack)?;
Ok(err.into_value())
})();
match built {
Ok(v) => ctx.throw(v),
Err(_) => throw_named(ctx, name, message),
}
}
#[must_use]
pub fn ms_f64_to_u64(ms: f64) -> u64 {
if ms <= 0.0 {
return 0;
}
#[allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_precision_loss
)]
{
ms.min(u64::MAX as f64) as u64
}
}
pub fn parse_timeout_number_or_bag<'js>(
ctx: &Ctx<'js>,
options: rquickjs::function::Opt<rquickjs::Value<'js>>,
) -> rquickjs::Result<Option<u64>> {
match options.0 {
Some(v) if v.is_number() => Ok(v.as_number().map(ms_f64_to_u64)),
Some(v) if v.is_object() => {
#[derive(serde::Deserialize, Default)]
#[serde(rename_all = "camelCase", default)]
struct JsTimeoutBag {
timeout: Option<f64>,
}
let parsed: JsTimeoutBag = serde_from_js(ctx, v)?;
Ok(parsed.timeout.map(ms_f64_to_u64))
},
_ => Ok(None),
}
}
pub fn serde_to_js<'js, T: Serialize>(ctx: &Ctx<'js>, value: &T) -> rquickjs::Result<Value<'js>> {
rquickjs_serde::to_value(ctx.clone(), value)
.map_err(|e| rquickjs::Error::new_from_js_message("serde", "serialize", e.to_string()))
}
pub fn name_value_array_to_js<'js, S: AsRef<str>>(ctx: &Ctx<'js>, pairs: &[(S, S)]) -> rquickjs::Result<Value<'js>> {
#[derive(Serialize)]
struct NameValue<'a> {
name: &'a str,
value: &'a str,
}
let view: Vec<NameValue<'_>> = pairs
.iter()
.map(|(n, v)| NameValue {
name: n.as_ref(),
value: v.as_ref(),
})
.collect();
serde_to_js(ctx, &view)
}
pub fn serde_from_js<'js, T: DeserializeOwned>(_ctx: &Ctx<'js>, value: Value<'js>) -> rquickjs::Result<T> {
rquickjs_serde::from_value(value)
.map_err(|e| rquickjs::Error::new_from_js_message("serde", "deserialize", e.to_string()))
}
pub fn parse_opt_bag<'js, T: DeserializeOwned>(
ctx: &Ctx<'js>,
value: rquickjs::function::Opt<Value<'js>>,
) -> rquickjs::Result<Option<T>> {
match value.0 {
Some(v) if !v.is_undefined() && !v.is_null() => Ok(Some(serde_from_js(ctx, v)?)),
_ => Ok(None),
}
}
fn define_own<'js, V: rquickjs::IntoJs<'js>>(obj: &Object<'js>, key: &str, value: V) -> rquickjs::Result<()> {
obj.prop(key, Property::from(value).writable().enumerable().configurable())
}
pub struct Null<T>(pub Option<T>);
impl<'js, T: rquickjs::IntoJs<'js>> rquickjs::IntoJs<'js> for Null<T> {
fn into_js(self, ctx: &Ctx<'js>) -> rquickjs::Result<Value<'js>> {
match self.0 {
Some(v) => v.into_js(ctx),
None => Ok(Value::new_null(ctx.clone())),
}
}
}
pub fn json_to_js<'js>(ctx: &Ctx<'js>, v: &serde_json::Value) -> rquickjs::Result<Value<'js>> {
match v {
serde_json::Value::Null => Ok(Value::new_null(ctx.clone())),
serde_json::Value::Bool(b) => Ok(Value::new_bool(ctx.clone(), *b)),
serde_json::Value::Number(n) => {
let f = n.as_f64().unwrap_or(f64::NAN);
if let Some(i) = f64_as_exact_i32(f) {
Ok(Value::new_int(ctx.clone(), i))
} else {
Ok(Value::new_number(ctx.clone(), f))
}
},
serde_json::Value::String(s) => Ok(rquickjs::String::from_str(ctx.clone(), s)?.into_value()),
serde_json::Value::Array(items) => {
let arr = rquickjs::Array::new(ctx.clone())?;
for (i, item) in items.iter().enumerate() {
arr.set(i, json_to_js(ctx, item)?)?;
}
Ok(arr.into_value())
},
serde_json::Value::Object(map) => {
let obj = Object::new(ctx.clone())?;
for (k, val) in map {
define_own(&obj, k.as_str(), json_to_js(ctx, val)?)?;
}
Ok(obj.into_value())
},
}
}
fn f64_as_exact_i32(n: f64) -> Option<i32> {
if n.is_finite() && n.fract() == 0.0 && n >= f64::from(i32::MIN) && n <= f64::from(i32::MAX) {
let trunc = n.trunc();
i32::try_from(trunc as i64).ok()
} else {
None
}
}
#[must_use]
pub fn value_to_json<'js>(_ctx: &Ctx<'js>, value: Value<'js>) -> Option<serde_json::Value> {
rquickjs_serde::from_value::<JsonInter>(value)
.ok()
.map(JsonInter::into_json)
}
enum JsonInter {
Null,
Bool(bool),
I64(i64),
U64(u64),
F64(f64),
Str(String),
Arr(Vec<JsonInter>),
Obj(Vec<(String, JsonInter)>),
}
impl JsonInter {
fn into_json(self) -> serde_json::Value {
use serde_json::Value;
match self {
Self::Null => Value::Null,
Self::Bool(b) => Value::Bool(b),
Self::I64(n) => Value::Number(n.into()),
Self::U64(n) => Value::Number(n.into()),
Self::F64(f) => serde_json::Number::from_f64(f).map_or(Value::Null, Value::Number),
Self::Str(s) => Value::String(s),
Self::Arr(a) => Value::Array(a.into_iter().map(Self::into_json).collect()),
Self::Obj(o) => Value::Object(o.into_iter().map(|(k, v)| (k, v.into_json())).collect()),
}
}
}
impl<'de> serde::Deserialize<'de> for JsonInter {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
struct V;
impl<'de> serde::de::Visitor<'de> for V {
type Value = JsonInter;
fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("any JSON value")
}
fn visit_unit<E>(self) -> Result<JsonInter, E> {
Ok(JsonInter::Null)
}
fn visit_none<E>(self) -> Result<JsonInter, E> {
Ok(JsonInter::Null)
}
fn visit_bool<E>(self, v: bool) -> Result<JsonInter, E> {
Ok(JsonInter::Bool(v))
}
fn visit_i64<E>(self, v: i64) -> Result<JsonInter, E> {
Ok(JsonInter::I64(v))
}
fn visit_u64<E>(self, v: u64) -> Result<JsonInter, E> {
Ok(JsonInter::U64(v))
}
fn visit_f64<E>(self, v: f64) -> Result<JsonInter, E> {
Ok(JsonInter::F64(v))
}
fn visit_str<E>(self, v: &str) -> Result<JsonInter, E> {
Ok(JsonInter::Str(v.to_owned()))
}
fn visit_string<E>(self, v: String) -> Result<JsonInter, E> {
Ok(JsonInter::Str(v))
}
fn visit_seq<A: serde::de::SeqAccess<'de>>(self, mut a: A) -> Result<JsonInter, A::Error> {
let mut out = Vec::new();
while let Some(e) = a.next_element()? {
out.push(e);
}
Ok(JsonInter::Arr(out))
}
fn visit_map<A: serde::de::MapAccess<'de>>(self, mut m: A) -> Result<JsonInter, A::Error> {
let mut out = Vec::new();
while let Some((k, v)) = m.next_entry()? {
out.push((k, v));
}
Ok(JsonInter::Obj(out))
}
}
d.deserialize_any(V)
}
}