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