use smallvec::smallvec;
use crate::{
context::{internal::ContextInternal, Context, Cx},
handle::{Handle, Root},
result::{NeonResult, Throw},
sys::{self, raw},
types::{
build,
extract::{TryFromJs, TryIntoJs},
function::{BindOptions, CallOptions},
private::ValueInternal,
utf8::Utf8,
JsFunction, JsUndefined, JsValue, Value,
},
};
#[cfg(feature = "napi-6")]
use crate::{result::JsResult, types::JsArray};
pub trait PropertyKey: Copy {
unsafe fn get_from<'c, C: Context<'c>>(
self,
cx: &mut C,
out: &mut raw::Local,
obj: raw::Local,
) -> bool;
unsafe fn set_from<'c, C: Context<'c>>(
self,
cx: &mut C,
out: &mut bool,
obj: raw::Local,
val: raw::Local,
) -> bool;
}
impl PropertyKey for u32 {
unsafe fn get_from<'c, C: Context<'c>>(
self,
cx: &mut C,
out: &mut raw::Local,
obj: raw::Local,
) -> bool {
sys::object::get_index(out, cx.env().to_raw(), obj, self)
}
unsafe fn set_from<'c, C: Context<'c>>(
self,
cx: &mut C,
out: &mut bool,
obj: raw::Local,
val: raw::Local,
) -> bool {
sys::object::set_index(out, cx.env().to_raw(), obj, self, val)
}
}
impl<'a, K: Value> PropertyKey for Handle<'a, K> {
unsafe fn get_from<'c, C: Context<'c>>(
self,
cx: &mut C,
out: &mut raw::Local,
obj: raw::Local,
) -> bool {
let env = cx.env().to_raw();
sys::object::get(out, env, obj, self.to_local())
}
unsafe fn set_from<'c, C: Context<'c>>(
self,
cx: &mut C,
out: &mut bool,
obj: raw::Local,
val: raw::Local,
) -> bool {
let env = cx.env().to_raw();
sys::object::set(out, env, obj, self.to_local(), val)
}
}
impl<'a> PropertyKey for &'a str {
unsafe fn get_from<'c, C: Context<'c>>(
self,
cx: &mut C,
out: &mut raw::Local,
obj: raw::Local,
) -> bool {
let (ptr, len) = Utf8::from(self).into_small_unwrap().lower();
let env = cx.env().to_raw();
sys::object::get_string(env, out, obj, ptr, len)
}
unsafe fn set_from<'c, C: Context<'c>>(
self,
cx: &mut C,
out: &mut bool,
obj: raw::Local,
val: raw::Local,
) -> bool {
let (ptr, len) = Utf8::from(self).into_small_unwrap().lower();
let env = cx.env().to_raw();
sys::object::set_string(env, out, obj, ptr, len, val)
}
}
pub struct PropOptions<'a, 'cx, O, K>
where
'cx: 'a,
O: Object,
K: PropertyKey,
{
pub(crate) cx: &'a mut Cx<'cx>,
pub(crate) this: Handle<'cx, O>,
pub(crate) key: K,
}
impl<'a, 'cx, O, K> PropOptions<'a, 'cx, O, K>
where
'cx: 'a,
O: Object,
K: PropertyKey,
{
pub fn this(&self) -> Handle<'cx, O> {
self.this
}
pub fn prop(&mut self, key: K) -> &mut Self {
self.key = key;
self
}
pub fn get<R: TryFromJs<'cx>>(&mut self) -> NeonResult<R> {
let v = self.this.get_value(self.cx, self.key)?;
R::from_js(self.cx, v)
}
pub fn set<V: TryIntoJs<'cx>>(&mut self, v: V) -> NeonResult<&mut Self> {
let v = v.try_into_js(self.cx)?;
self.this.set(self.cx, self.key, v)?;
Ok(self)
}
pub fn set_with<R, F>(&mut self, f: F) -> NeonResult<&mut Self>
where
R: TryIntoJs<'cx>,
F: FnOnce(&mut Cx<'cx>) -> R,
{
let v = f(self.cx).try_into_js(self.cx)?;
self.this.set(self.cx, self.key, v)?;
Ok(self)
}
pub fn bind(&'a mut self) -> NeonResult<BindOptions<'a, 'cx>> {
let callee: Handle<JsValue> = self.this.get(self.cx, self.key)?;
let this = Some(self.this.upcast());
Ok(BindOptions {
cx: self.cx,
callee,
this,
args: smallvec![],
})
}
}
pub trait Object: Value {
fn prop<'a, 'cx: 'a, K: PropertyKey>(
&self,
cx: &'a mut Cx<'cx>,
key: K,
) -> PropOptions<'a, 'cx, Self, K> {
let this: Handle<'_, Self> =
Handle::new_internal(unsafe { ValueInternal::from_local(cx.env(), self.to_local()) });
PropOptions { cx, this, key }
}
fn method<'a, 'cx: 'a, K: PropertyKey>(
&self,
cx: &'a mut Cx<'cx>,
key: K,
) -> NeonResult<BindOptions<'a, 'cx>> {
let callee: Handle<JsValue> = self.prop(cx, key).get()?;
let this = Some(self.as_value(cx));
Ok(BindOptions {
cx,
callee,
this,
args: smallvec![],
})
}
#[deprecated(since = "TBD", note = "use `Object::prop()` instead")]
fn get_opt<'a, V: Value, C: Context<'a>, K: PropertyKey>(
&self,
cx: &mut C,
key: K,
) -> NeonResult<Option<Handle<'a, V>>> {
let v = self.get_value(cx, key)?;
if v.is_a::<JsUndefined, _>(cx) {
return Ok(None);
}
v.downcast_or_throw(cx).map(Some)
}
#[deprecated(since = "TBD", note = "use `Object::prop()` instead")]
fn get_value<'a, C: Context<'a>, K: PropertyKey>(
&self,
cx: &mut C,
key: K,
) -> NeonResult<Handle<'a, JsValue>> {
build(cx.env(), |out| unsafe {
key.get_from(cx, out, self.to_local())
})
}
#[deprecated(since = "TBD", note = "use `Object::prop()` instead")]
fn get<'a, V: Value, C: Context<'a>, K: PropertyKey>(
&self,
cx: &mut C,
key: K,
) -> NeonResult<Handle<'a, V>> {
self.get_value(cx, key)?.downcast_or_throw(cx)
}
#[cfg(feature = "napi-6")]
#[cfg_attr(docsrs, doc(cfg(feature = "napi-6")))]
fn get_own_property_names<'a, C: Context<'a>>(&self, cx: &mut C) -> JsResult<'a, JsArray> {
let env = cx.env();
build(cx.env(), |out| unsafe {
sys::object::get_own_property_names(out, env.to_raw(), self.to_local())
})
}
#[cfg(feature = "napi-8")]
fn freeze<'a, C: Context<'a>>(&self, cx: &mut C) -> NeonResult<&Self> {
let env = cx.env().to_raw();
let obj = self.to_local();
unsafe {
match sys::object::freeze(env, obj) {
Ok(()) => Ok(self),
Err(sys::Status::PendingException) => Err(Throw::new()),
_ => cx.throw_type_error("object cannot be frozen"),
}
}
}
#[cfg(feature = "napi-8")]
fn seal<'a, C: Context<'a>>(&self, cx: &mut C) -> NeonResult<&Self> {
let env = cx.env().to_raw();
let obj = self.to_local();
unsafe {
match sys::object::seal(env, obj) {
Ok(()) => Ok(self),
Err(sys::Status::PendingException) => Err(Throw::new()),
_ => cx.throw_type_error("object cannot be sealed"),
}
}
}
#[deprecated(since = "TBD", note = "use `Object::prop()` instead")]
fn set<'a, C: Context<'a>, K: PropertyKey, W: Value>(
&self,
cx: &mut C,
key: K,
val: Handle<W>,
) -> NeonResult<bool> {
let mut result = false;
unsafe {
if key.set_from(cx, &mut result, self.to_local(), val.to_local()) {
Ok(result)
} else {
Err(Throw::new())
}
}
}
fn root<'a, C: Context<'a>>(&self, cx: &mut C) -> Root<Self> {
Root::new(cx, self)
}
#[deprecated(since = "TBD", note = "use `Object::method()` instead")]
fn call_method_with<'a, C, K>(&self, cx: &mut C, method: K) -> NeonResult<CallOptions<'a>>
where
C: Context<'a>,
K: PropertyKey,
{
let mut options = self.get::<JsFunction, _, _>(cx, method)?.call_with(cx);
options.this(JsValue::new_internal(self.to_local()));
Ok(options)
}
}