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