neon/object/
mod.rs

1//! Traits for working with JavaScript objects.
2//!
3//! This module defines the [`Object`] trait, which is implemented
4//! by all object types in the [JavaScript type hierarchy][hierarchy]. This
5//! trait provides key operations in the semantics of JavaScript objects,
6//! such as getting and setting an object's properties.
7//!
8//! ## Property Keys
9//!
10//! Object properties are accessed by a _property key_, which in JavaScript
11//! can be a string or [symbol][symbol]. (Neon does not yet have support for
12//! symbols.) For convenience, the [`PropertyKey`] trait allows
13//! Neon programs to use various Rust string types, as well as numeric types,
14//! as keys when accessing object properties, converting the keys to strings
15//! as necessary:
16//!
17//! ```
18//! # use neon::prelude::*;
19//! fn set_and_check<'cx>(
20//!     cx: &mut Cx<'cx>,
21//!     obj: Handle<'cx, JsObject>
22//! ) -> JsResult<'cx, JsValue> {
23//!     // set property "17" with integer shorthand
24//!     obj.prop(cx, 17).set("hello")?;
25//!     // get property "17" with string shorthand
26//!     // returns the same value ("hello!")
27//!     obj.prop(cx, "17").get()
28//! }
29//! ```
30//!
31//! [hierarchy]: crate::types#the-javascript-type-hierarchy
32//! [symbol]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol
33
34use smallvec::smallvec;
35
36use crate::{
37    context::{internal::ContextInternal, Context, Cx},
38    handle::{Handle, Root},
39    result::{NeonResult, Throw},
40    sys::{self, raw},
41    types::{
42        build,
43        extract::{TryFromJs, TryIntoJs},
44        function::{BindOptions, CallOptions},
45        private::ValueInternal,
46        utf8::Utf8,
47        JsFunction, JsUndefined, JsValue, Value,
48    },
49};
50
51#[cfg(feature = "napi-6")]
52use crate::{result::JsResult, types::JsArray};
53
54/// A property key in a JavaScript object.
55pub trait PropertyKey: Copy {
56    unsafe fn get_from<'c, C: Context<'c>>(
57        self,
58        cx: &mut C,
59        out: &mut raw::Local,
60        obj: raw::Local,
61    ) -> bool;
62
63    unsafe fn set_from<'c, C: Context<'c>>(
64        self,
65        cx: &mut C,
66        out: &mut bool,
67        obj: raw::Local,
68        val: raw::Local,
69    ) -> bool;
70}
71
72impl PropertyKey for u32 {
73    unsafe fn get_from<'c, C: Context<'c>>(
74        self,
75        cx: &mut C,
76        out: &mut raw::Local,
77        obj: raw::Local,
78    ) -> bool {
79        sys::object::get_index(out, cx.env().to_raw(), obj, self)
80    }
81
82    unsafe fn set_from<'c, C: Context<'c>>(
83        self,
84        cx: &mut C,
85        out: &mut bool,
86        obj: raw::Local,
87        val: raw::Local,
88    ) -> bool {
89        sys::object::set_index(out, cx.env().to_raw(), obj, self, val)
90    }
91}
92
93impl<'a, K: Value> PropertyKey for Handle<'a, K> {
94    unsafe fn get_from<'c, C: Context<'c>>(
95        self,
96        cx: &mut C,
97        out: &mut raw::Local,
98        obj: raw::Local,
99    ) -> bool {
100        let env = cx.env().to_raw();
101
102        sys::object::get(out, env, obj, self.to_local())
103    }
104
105    unsafe fn set_from<'c, C: Context<'c>>(
106        self,
107        cx: &mut C,
108        out: &mut bool,
109        obj: raw::Local,
110        val: raw::Local,
111    ) -> bool {
112        let env = cx.env().to_raw();
113
114        sys::object::set(out, env, obj, self.to_local(), val)
115    }
116}
117
118impl<'a> PropertyKey for &'a str {
119    unsafe fn get_from<'c, C: Context<'c>>(
120        self,
121        cx: &mut C,
122        out: &mut raw::Local,
123        obj: raw::Local,
124    ) -> bool {
125        let (ptr, len) = Utf8::from(self).into_small_unwrap().lower();
126        let env = cx.env().to_raw();
127
128        sys::object::get_string(env, out, obj, ptr, len)
129    }
130
131    unsafe fn set_from<'c, C: Context<'c>>(
132        self,
133        cx: &mut C,
134        out: &mut bool,
135        obj: raw::Local,
136        val: raw::Local,
137    ) -> bool {
138        let (ptr, len) = Utf8::from(self).into_small_unwrap().lower();
139        let env = cx.env().to_raw();
140
141        sys::object::set_string(env, out, obj, ptr, len, val)
142    }
143}
144
145/// A builder for accessing an object property.
146///
147/// The builder methods make it convenient to get and set properties
148/// as well as to bind and call methods.
149/// ```
150/// # use neon::prelude::*;
151/// # fn foo(mut cx: FunctionContext) -> JsResult<JsString> {
152/// # let obj: Handle<JsObject> = cx.argument(0)?;
153/// let x: f64 = obj
154///     .prop(&mut cx, "x")
155///     .get()?;
156///
157/// obj.prop(&mut cx, "y")
158///     .set(x)?;
159///
160/// let s: String = obj.method(&mut cx, "toString")?.call()?;
161/// # Ok(cx.string(s))
162/// # }
163/// ```
164pub struct PropOptions<'a, 'cx, O, K>
165where
166    'cx: 'a,
167    O: Object,
168    K: PropertyKey,
169{
170    pub(crate) cx: &'a mut Cx<'cx>,
171    pub(crate) this: Handle<'cx, O>,
172    pub(crate) key: K,
173}
174
175impl<'a, 'cx, O, K> PropOptions<'a, 'cx, O, K>
176where
177    'cx: 'a,
178    O: Object,
179    K: PropertyKey,
180{
181    /// Returns the original object from which the property was accessed.
182    pub fn this(&self) -> Handle<'cx, O> {
183        self.this
184    }
185
186    /// Updates the property key.
187    ///
188    /// This method is useful for chaining multiple property assignments:
189    ///
190    /// ```
191    /// # use neon::prelude::*;
192    /// # fn foo(mut cx: FunctionContext) -> JsResult<JsObject> {
193    /// let obj = cx.empty_object()
194    ///     .prop(&mut cx, "x")
195    ///     .set(1)?
196    ///     .prop("y")
197    ///     .set(2)?
198    ///     .prop("color")
199    ///     .set("blue")?
200    ///     .this();
201    /// # Ok(obj)
202    /// # }
203    /// ```
204    pub fn prop(&mut self, key: K) -> &mut Self {
205        self.key = key;
206        self
207    }
208
209    /// Gets the property from the object and attempts to convert it to a Rust value.
210    ///
211    /// May throw an exception either during accessing the property or converting the
212    /// result type.
213    pub fn get<R: TryFromJs<'cx>>(&mut self) -> NeonResult<R> {
214        let v = self.this.get_value(self.cx, self.key)?;
215        R::from_js(self.cx, v)
216    }
217
218    /// Sets the property on the object to a value converted from Rust.
219    ///
220    /// May throw an exception either during converting the value or setting the property.
221    pub fn set<V: TryIntoJs<'cx>>(&mut self, v: V) -> NeonResult<&mut Self> {
222        let v = v.try_into_js(self.cx)?;
223        self.this.set(self.cx, self.key, v)?;
224        Ok(self)
225    }
226
227    /// Sets the property on the object to a value computed from a closure.
228    ///
229    /// May throw an exception either during converting the value or setting the property.
230    pub fn set_with<R, F>(&mut self, f: F) -> NeonResult<&mut Self>
231    where
232        R: TryIntoJs<'cx>,
233        F: FnOnce(&mut Cx<'cx>) -> R,
234    {
235        let v = f(self.cx).try_into_js(self.cx)?;
236        self.this.set(self.cx, self.key, v)?;
237        Ok(self)
238    }
239
240    /// Gets the property from the object as a method and binds `this` to the object.
241    ///
242    /// May throw an exception when accessing the property.
243    ///
244    /// Defers checking that the method is callable until call time.
245    pub fn bind(&'a mut self) -> NeonResult<BindOptions<'a, 'cx>> {
246        let callee: Handle<JsValue> = self.this.get(self.cx, self.key)?;
247        let this = Some(self.this.upcast());
248        Ok(BindOptions {
249            cx: self.cx,
250            callee,
251            this,
252            args: smallvec![],
253        })
254    }
255}
256
257/// The trait of all object types.
258pub trait Object: Value {
259    /// Create a [`PropOptions`] for accessing a property.
260    ///
261    /// # Safety
262    ///
263    /// Because `cx` is a mutable reference, Neon guarantees it
264    /// is the context with the shortest possible lifetime, so
265    /// replacing the lifetime `'self` with `'cx` cannot extend
266    /// the lifetime of the property beyond the lifetime of the
267    /// object.
268    fn prop<'a, 'cx: 'a, K: PropertyKey>(
269        &self,
270        cx: &'a mut Cx<'cx>,
271        key: K,
272    ) -> PropOptions<'a, 'cx, Self, K> {
273        let this: Handle<'_, Self> =
274            Handle::new_internal(unsafe { ValueInternal::from_local(cx.env(), self.to_local()) });
275        PropOptions { cx, this, key }
276    }
277
278    /// Gets a property from the object as a method and binds `this` to the object.
279    ///
280    /// May throw an exception either from accessing the property.
281    ///
282    /// Defers checking that the method is callable until call time.
283    fn method<'a, 'cx: 'a, K: PropertyKey>(
284        &self,
285        cx: &'a mut Cx<'cx>,
286        key: K,
287    ) -> NeonResult<BindOptions<'a, 'cx>> {
288        let callee: Handle<JsValue> = self.prop(cx, key).get()?;
289        let this = Some(self.as_value(cx));
290        Ok(BindOptions {
291            cx,
292            callee,
293            this,
294            args: smallvec![],
295        })
296    }
297
298    #[deprecated(since = "TBD", note = "use `Object::prop()` instead")]
299    fn get_opt<'a, V: Value, C: Context<'a>, K: PropertyKey>(
300        &self,
301        cx: &mut C,
302        key: K,
303    ) -> NeonResult<Option<Handle<'a, V>>> {
304        let v = self.get_value(cx, key)?;
305
306        if v.is_a::<JsUndefined, _>(cx) {
307            return Ok(None);
308        }
309
310        v.downcast_or_throw(cx).map(Some)
311    }
312
313    #[deprecated(since = "TBD", note = "use `Object::prop()` instead")]
314    fn get_value<'a, C: Context<'a>, K: PropertyKey>(
315        &self,
316        cx: &mut C,
317        key: K,
318    ) -> NeonResult<Handle<'a, JsValue>> {
319        build(cx.env(), |out| unsafe {
320            key.get_from(cx, out, self.to_local())
321        })
322    }
323
324    #[deprecated(since = "TBD", note = "use `Object::prop()` instead")]
325    fn get<'a, V: Value, C: Context<'a>, K: PropertyKey>(
326        &self,
327        cx: &mut C,
328        key: K,
329    ) -> NeonResult<Handle<'a, V>> {
330        self.get_value(cx, key)?.downcast_or_throw(cx)
331    }
332
333    #[cfg(feature = "napi-6")]
334    #[cfg_attr(docsrs, doc(cfg(feature = "napi-6")))]
335    fn get_own_property_names<'a, C: Context<'a>>(&self, cx: &mut C) -> JsResult<'a, JsArray> {
336        let env = cx.env();
337
338        build(cx.env(), |out| unsafe {
339            sys::object::get_own_property_names(out, env.to_raw(), self.to_local())
340        })
341    }
342
343    #[cfg(feature = "napi-8")]
344    fn freeze<'a, C: Context<'a>>(&self, cx: &mut C) -> NeonResult<&Self> {
345        let env = cx.env().to_raw();
346        let obj = self.to_local();
347        unsafe {
348            match sys::object::freeze(env, obj) {
349                Ok(()) => Ok(self),
350                Err(sys::Status::PendingException) => Err(Throw::new()),
351                _ => cx.throw_type_error("object cannot be frozen"),
352            }
353        }
354    }
355
356    #[cfg(feature = "napi-8")]
357    fn seal<'a, C: Context<'a>>(&self, cx: &mut C) -> NeonResult<&Self> {
358        let env = cx.env().to_raw();
359        let obj = self.to_local();
360        unsafe {
361            match sys::object::seal(env, obj) {
362                Ok(()) => Ok(self),
363                Err(sys::Status::PendingException) => Err(Throw::new()),
364                _ => cx.throw_type_error("object cannot be sealed"),
365            }
366        }
367    }
368
369    #[deprecated(since = "TBD", note = "use `Object::prop()` instead")]
370    fn set<'a, C: Context<'a>, K: PropertyKey, W: Value>(
371        &self,
372        cx: &mut C,
373        key: K,
374        val: Handle<W>,
375    ) -> NeonResult<bool> {
376        let mut result = false;
377        unsafe {
378            if key.set_from(cx, &mut result, self.to_local(), val.to_local()) {
379                Ok(result)
380            } else {
381                Err(Throw::new())
382            }
383        }
384    }
385
386    fn root<'a, C: Context<'a>>(&self, cx: &mut C) -> Root<Self> {
387        Root::new(cx, self)
388    }
389
390    #[deprecated(since = "TBD", note = "use `Object::method()` instead")]
391    fn call_method_with<'a, C, K>(&self, cx: &mut C, method: K) -> NeonResult<CallOptions<'a>>
392    where
393        C: Context<'a>,
394        K: PropertyKey,
395    {
396        let mut options = self.get::<JsFunction, _, _>(cx, method)?.call_with(cx);
397        options.this(JsValue::new_internal(self.to_local()));
398        Ok(options)
399    }
400}