use crate::{
Args, hold, json,
quickjs::{
Ctx, Exception, Function, Object, String as JSString, Value,
atom::PredefinedAtom,
function::This,
prelude::{MutFn, Rest},
qjs::JS_GetProperty,
},
to_js_error, val_to_string,
};
use simd_json::Error as SError;
use anyhow::{Result, anyhow, bail};
use std::{sync::OnceLock, time::SystemTime};
static DEFAULT_PARSE_KEY: OnceLock<String> = OnceLock::new();
pub(crate) fn register<'js>(this: Ctx<'js>) -> Result<()> {
let global = this.globals();
let json: Object = global.get("JSON")?;
let default_parse: Function = json.get("parse")?;
let millis = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)?
.subsec_millis();
let default_parse_key = DEFAULT_PARSE_KEY.get_or_init(|| format!("__javy_{millis}_json_parse"));
global.set(default_parse_key, default_parse)?;
let parse = Function::new(
this.clone(),
MutFn::new(move |cx: Ctx<'js>, args: Rest<Value<'js>>| {
call_json_parse(hold!(cx.clone(), args)).map_err(|e| to_js_error(cx, e))
}),
)?;
parse.set_length(2)?;
parse.set_name("parse")?;
let stringify = Function::new(
this.clone(),
MutFn::new(|cx: Ctx<'js>, args: Rest<Value<'js>>| {
call_json_stringify(hold!(cx.clone(), args)).map_err(|e| to_js_error(cx, e))
}),
)?;
stringify.set_name("stringify")?;
stringify.set_length(3)?;
let global = this.globals();
let json: Object = global.get("JSON")?;
json.set("parse", parse)?;
json.set("stringify", stringify)?;
Ok(())
}
fn call_json_parse(args: Args<'_>) -> Result<Value<'_>> {
let (this, args) = args.release();
match args.len() {
0 => bail!(Exception::throw_syntax(
&this,
"\"undefined\" is not valid JSON"
)),
1 => {
let val = args[0].clone();
if val.is_number() || val.is_null() {
return Ok(val);
}
if val.is_symbol() {
bail!(Exception::throw_type(&this, "Expected string primitive"));
}
let mut string = val_to_string(&this, args[0].clone())?;
let bytes = unsafe { string.as_bytes_mut() };
json::parse(this.clone(), bytes).map_err(|original| {
if original.downcast_ref::<SError>().is_none() {
return original;
}
let e = match original.downcast_ref::<SError>() {
Some(e) => e.to_string(),
None => "JSON parse error".into(),
};
anyhow!(Exception::throw_syntax(&this, &e))
})
}
_ => {
let default: Function = this.globals().get(DEFAULT_PARSE_KEY.get().unwrap())?;
default
.call((args[0].clone(), args[1].clone()))
.map_err(|e| anyhow!(e))
}
}
}
fn call_json_stringify(args: Args<'_>) -> Result<Value<'_>> {
let (this, args) = args.release();
match args.len() {
0 => Ok(Value::new_undefined(this.clone())),
1 => {
let arg = args[0].clone();
let val: Value = if arg.is_object() {
if let Some(f) = get_to_json(&arg) {
f.call((
This(arg.clone()),
JSString::from_str(arg.ctx().clone(), "")?.into_value(),
))?
} else {
arg.clone()
}
} else {
arg.clone()
};
if val.is_function() || val.is_undefined() || val.is_symbol() {
return Ok(Value::new_undefined(arg.ctx().clone()));
}
let bytes = json::stringify(val.clone())?;
let str = String::from_utf8(bytes)?;
let str = JSString::from_str(this, &str)?;
Ok(str.into_value())
}
2 => Ok(this
.json_stringify_replacer(args[0].clone(), args[1].clone())?
.map_or_else(
|| Value::new_undefined(this.clone()),
|str| str.into_value(),
)),
_ => Ok(this
.json_stringify_replacer_space(args[0].clone(), args[1].clone(), args[2].clone())?
.map_or_else(
|| Value::new_undefined(this.clone()),
|str| str.into_value(),
)),
}
}
fn get_to_json<'a>(value: &Value<'a>) -> Option<Function<'a>> {
let f = unsafe {
JS_GetProperty(
value.ctx().as_raw().as_ptr(),
value.as_raw(),
PredefinedAtom::ToJSON as u32,
)
};
let f = unsafe { Value::from_raw(value.ctx().clone(), f) };
if f.is_function()
&& let Some(f) = f.into_function()
{
Some(f)
} else {
None
}
}