nodex_api/
descriptor.rs

1use crate::{api, prelude::*};
2use std::mem::MaybeUninit;
3
4#[derive(Clone, Debug)]
5#[repr(C)]
6pub struct NapiPropertyDescriptor(napi_property_descriptor);
7
8impl AsRef<napi_property_descriptor> for NapiPropertyDescriptor {
9    fn as_ref(&self) -> &napi_property_descriptor {
10        &self.0
11    }
12}
13
14impl std::ops::Deref for NapiPropertyDescriptor {
15    type Target = napi_property_descriptor;
16    fn deref(&self) -> &napi_property_descriptor {
17        &self.0
18    }
19}
20
21impl NapiPropertyDescriptor {
22    pub fn raw(&self) -> &napi_property_descriptor {
23        &self.0
24    }
25}
26
27/// The DescriptorBuild for value.
28pub struct DescriptorValueBuilder {
29    pub utf8name: Option<String>,
30    pub name: napi_value,
31    pub value: napi_value,
32    pub attributes: NapiPropertyAttributes,
33}
34
35/// The DescriptorBuild for method.
36/// NB: there seems no way to reclaim the napi_property_descriptor.data, so it is leaked.
37#[allow(clippy::type_complexity)]
38pub struct DescriptorMethodBuilder<T: FromJsArgs, R: NapiValueT> {
39    pub utf8name: Option<String>,
40    pub name: napi_value,
41    pub method: Option<Box<dyn FnMut(JsObject, T) -> NapiResult<R> + 'static>>,
42    pub attributes: NapiPropertyAttributes,
43}
44
45/// The DescriptorBuild for accessor.
46/// NB: there seems no way to reclaim the napi_property_descriptor.data, so it is leaked.
47#[allow(clippy::type_complexity)]
48pub struct DescriptorAccessorBuilder<T: NapiValueT, R: NapiValueT> {
49    pub utf8name: Option<String>,
50    pub name: napi_value,
51    pub getter: Option<Box<dyn FnMut(JsObject) -> NapiResult<R> + 'static>>,
52    pub setter: Option<Box<dyn FnMut(JsObject, T) -> NapiResult<()> + 'static>>,
53    pub attributes: NapiPropertyAttributes,
54}
55
56impl DescriptorValueBuilder {
57    pub fn new() -> DescriptorValueBuilder {
58        DescriptorValueBuilder {
59            utf8name: None,
60            name: std::ptr::null_mut(),
61            value: std::ptr::null_mut(),
62            attributes: NapiPropertyAttributes::Default,
63        }
64    }
65
66    /// Optional string describing the key for the property, encoded as UTF8. One of utf8name or
67    /// name must be provided for the property.
68    pub fn with_utf8name(mut self, name: impl Into<String>) -> Self {
69        self.utf8name.replace(name.into());
70        self
71    }
72
73    /// Optional napi_value that points to a JavaScript string or symbol to be used as the key for
74    /// the property. One of utf8name or name must be provided for the property.
75    pub fn with_name(mut self, name: impl NapiValueT) -> Self {
76        let name = name.value();
77        if let (Ok(name_string), Ok(name_symbol)) = (
78            unsafe { name.cast::<JsString>() }.check(),
79            unsafe { name.cast::<JsSymbol>() }.check(),
80        ) {
81            if name_string || name_symbol {
82                self.name = name.raw();
83            }
84        }
85        self
86    }
87
88    /// The value that's retrieved by a get access of the property if the property is a data
89    /// property. If this is passed in, set getter, setter, method and data to NULL (since these
90    /// members won't be used).
91    pub fn with_value(mut self, value: impl NapiValueT) -> Self {
92        self.value = value.raw();
93        self
94    }
95
96    /// The attributes associated with the particular property. See napi_property_attributes.
97    pub fn with_attribute(mut self, attribute: NapiPropertyAttributes) -> Self {
98        self.attributes |= attribute;
99        self
100    }
101
102    /// build finale `NapiPropertyDescriptor`
103    pub fn build(mut self) -> NapiResult<NapiPropertyDescriptor> {
104        let utf8name = if let Some(name) = self.utf8name {
105            std::ffi::CString::new(name)
106                .map_err(|_| NapiStatus::StringExpected)?
107                .into_raw()
108        } else {
109            std::ptr::null()
110        };
111
112        let name = self.name;
113
114        // NB: panic if utf8name and name is both null
115        if (utf8name.is_null() && name.is_null()) {
116            return Err(NapiStatus::InvalidArg);
117        }
118
119        let method = None;
120        let getter = None;
121        let setter = None;
122
123        let value = self.value;
124        let attributes = self.attributes.bits();
125
126        Ok(NapiPropertyDescriptor(napi_property_descriptor {
127            utf8name,
128            name,
129            method,
130            getter,
131            setter,
132            value,
133            attributes,
134            data: std::ptr::null_mut(),
135        }))
136    }
137}
138
139impl<T: FromJsArgs, R: NapiValueT> DescriptorMethodBuilder<T, R> {
140    pub fn new() -> Self {
141        Self {
142            utf8name: None,
143            name: std::ptr::null_mut(),
144            method: None,
145            attributes: NapiPropertyAttributes::Default,
146        }
147    }
148
149    /// Optional string describing the key for the property, encoded as UTF8. One of utf8name or
150    /// name must be provided for the property.
151    pub fn with_utf8name(mut self, name: impl Into<String>) -> Self {
152        self.utf8name.replace(name.into());
153        self
154    }
155
156    /// Optional napi_value that points to a JavaScript string or symbol to be used as the key for
157    /// the property. One of utf8name or name must be provided for the property.
158    pub fn with_name(mut self, name: impl NapiValueT) -> Self {
159        let name = name.value();
160        if let (Ok(name_string), Ok(name_symbol)) = (
161            unsafe { name.cast::<JsString>() }.check(),
162            unsafe { name.cast::<JsSymbol>() }.check(),
163        ) {
164            if name_string || name_symbol {
165                self.name = name.raw();
166            }
167        }
168        self
169    }
170
171    /// Set this to make the property descriptor object's value property to be a JavaScript
172    /// function represented by method. If this is passed in, set value, getter and setter to NULL
173    /// (since these members won't be used). napi_callback provides more details.
174    pub fn with_method(
175        mut self,
176        method: impl FnMut(JsObject, T) -> NapiResult<R> + 'static,
177    ) -> Self {
178        self.method = Some(Box::new(method));
179        self
180    }
181
182    /// The attributes associated with the particular property. See napi_property_attributes.
183    pub fn with_attribute(mut self, attribute: NapiPropertyAttributes) -> Self {
184        self.attributes |= attribute;
185        self
186    }
187
188    /// build finale `NapiPropertyDescriptor`
189    #[allow(clippy::type_complexity)]
190    pub fn build(mut self) -> NapiResult<NapiPropertyDescriptor> {
191        let utf8name = if let Some(name) = self.utf8name {
192            std::ffi::CString::new(name)
193                .map_err(|_| NapiStatus::StringExpected)?
194                .into_raw()
195        } else {
196            std::ptr::null()
197        };
198
199        let name = self.name;
200
201        // NB: panic if utf8name and name is both null
202        if (utf8name.is_null() && name.is_null()) {
203            return Err(NapiStatus::InvalidArg);
204        }
205
206        extern "C" fn method_trampoline<T: FromJsArgs, R: NapiValueT>(
207            env: NapiEnv,
208            info: napi_callback_info,
209        ) -> napi_value {
210            let mut data = MaybeUninit::uninit();
211            let mut this = MaybeUninit::uninit();
212
213            let (argc, argv, this, mut func) = unsafe {
214                let mut argc = T::len();
215                let mut argv = vec![std::ptr::null_mut(); T::len()];
216
217                let status = api::napi_get_cb_info(
218                    env,
219                    info,
220                    &mut argc,
221                    argv.as_mut_ptr(),
222                    this.as_mut_ptr(),
223                    data.as_mut_ptr(),
224                );
225
226                let func: &mut Box<dyn FnMut(JsObject, T) -> NapiResult<R>> =
227                    std::mem::transmute(data);
228
229                (argc, argv, this.assume_init(), func)
230            };
231
232            let args = argv
233                .into_iter()
234                .map(|arg| JsValue::from_raw(env, arg))
235                .collect();
236            let this = JsObject::from_raw(env, this);
237
238            if let Ok(args) = T::from_js_args(JsArgs(args)) {
239                napi_r!(env, =func(this, args))
240            } else {
241                env.throw_error("wrong argument type!").unwrap();
242                env.undefined().unwrap().raw()
243            }
244        }
245
246        let method = Some(method_trampoline::<T, R> as _);
247        let data = if let Some(method) = self.method.take() {
248            Box::into_raw(Box::new(method)) as _
249        } else {
250            return Err(NapiStatus::InvalidArg);
251        };
252
253        let getter = None;
254        let setter = None;
255        let value = std::ptr::null_mut();
256
257        let attributes = self.attributes.bits();
258
259        Ok(NapiPropertyDescriptor(napi_property_descriptor {
260            utf8name,
261            name,
262            method,
263            getter,
264            setter,
265            value,
266            attributes,
267            data,
268        }))
269    }
270}
271
272impl<T: NapiValueT, R: NapiValueT> DescriptorAccessorBuilder<T, R> {
273    pub fn new() -> Self {
274        Self {
275            utf8name: None,
276            name: std::ptr::null_mut(),
277            getter: None,
278            setter: None,
279            attributes: NapiPropertyAttributes::Default,
280        }
281    }
282
283    /// Optional string describing the key for the property, encoded as UTF8. One of utf8name or
284    /// name must be provided for the property.
285    pub fn with_utf8name(mut self, name: impl Into<String>) -> Self {
286        self.utf8name.replace(name.into());
287        self
288    }
289
290    /// Optional napi_value that points to a JavaScript string or symbol to be used as the key for
291    /// the property. One of utf8name or name must be provided for the property.
292    pub fn with_name(mut self, name: impl NapiValueT) -> Self {
293        let name = name.value();
294        if let (Ok(name_string), Ok(name_symbol)) = (
295            unsafe { name.cast::<JsString>() }.check(),
296            unsafe { name.cast::<JsSymbol>() }.check(),
297        ) {
298            if name_string || name_symbol {
299                self.name = name.raw();
300            }
301        }
302        self
303    }
304
305    ///  A function to call when a get access of the property is performed. If this is passed in,
306    ///  set value and method to NULL (since these members won't be used). The given function is
307    ///  called implicitly by the runtime when the property is accessed from JavaScript code (or if
308    ///  a get on the property is performed using a Node-API call). napi_callback provides more
309    ///  details.
310    pub fn with_getter(mut self, getter: impl FnMut(JsObject) -> NapiResult<R> + 'static) -> Self {
311        self.getter = Some(Box::new(getter));
312        self
313    }
314
315    /// A function to call when a set access of the property is performed. If this is passed in,
316    /// set value and method to NULL (since these members won't be used). The given function is
317    /// called implicitly by the runtime when the property is set from JavaScript code (or if a set
318    /// on the property is performed using a Node-API call). napi_callback provides more details.
319    pub fn with_setter(
320        mut self,
321        setter: impl FnMut(JsObject, T) -> NapiResult<()> + 'static,
322    ) -> Self {
323        self.setter = Some(Box::new(setter));
324        self
325    }
326
327    /// The attributes associated with the particular property. See napi_property_attributes.
328    pub fn with_attribute(mut self, attribute: NapiPropertyAttributes) -> Self {
329        self.attributes |= attribute;
330        self
331    }
332
333    /// build finale `NapiPropertyDescriptor`
334    #[allow(clippy::type_complexity)]
335    pub fn build(mut self) -> NapiResult<NapiPropertyDescriptor> {
336        let utf8name = if let Some(name) = self.utf8name {
337            std::ffi::CString::new(name)
338                .map_err(|_| NapiStatus::StringExpected)?
339                .into_raw()
340        } else {
341            std::ptr::null()
342        };
343
344        let name = self.name;
345
346        // NB: panic if utf8name and name is both null
347        if (utf8name.is_null() && name.is_null()) {
348            return Err(NapiStatus::InvalidArg);
349        }
350
351        extern "C" fn getter_trampoline<T: NapiValueT, R: NapiValueT>(
352            env: NapiEnv,
353            info: napi_callback_info,
354        ) -> napi_value {
355            let mut argc = 0;
356            let mut argv = [std::ptr::null_mut(); 0];
357            let mut data = MaybeUninit::uninit();
358            let mut this = MaybeUninit::uninit();
359
360            let (argc, argv, this, mut func) = unsafe {
361                let status = api::napi_get_cb_info(
362                    env,
363                    info,
364                    &mut argc,
365                    argv.as_mut_ptr(),
366                    this.as_mut_ptr(),
367                    data.as_mut_ptr(),
368                );
369
370                // NB: the Function maybe called multiple times, so we can shoud leak the
371                // closure memory here.
372                //
373                // With napi >= 5, we can add a finalizer to this function.
374                let func: &mut (
375                    Option<Box<dyn FnMut(JsObject) -> NapiResult<R>>>,
376                    Option<Box<dyn FnMut(JsObject, T) -> NapiResult<()>>>,
377                ) = std::mem::transmute(data);
378
379                (argc, argv, this.assume_init(), func)
380            };
381
382            let this = JsObject::from_raw(env, this);
383
384            napi_r!(env, =func.0.as_mut().unwrap()(this))
385        }
386
387        let mut data = (None, None);
388
389        let getter = if let Some(getter) = self.getter {
390            data.0 = Some(getter);
391            Some(getter_trampoline::<T, R> as _)
392        } else {
393            None
394        };
395
396        extern "C" fn setter_trampoline<T: NapiValueT, R: NapiValueT>(
397            env: NapiEnv,
398            info: napi_callback_info,
399        ) -> napi_value {
400            let mut argc = 1;
401            let mut argv = [std::ptr::null_mut(); 1];
402            let mut data = MaybeUninit::uninit();
403            let mut this = MaybeUninit::uninit();
404
405            let (argc, argv, this, mut func) = unsafe {
406                let status = api::napi_get_cb_info(
407                    env,
408                    info,
409                    &mut argc,
410                    argv.as_mut_ptr(),
411                    this.as_mut_ptr(),
412                    data.as_mut_ptr(),
413                );
414
415                let func: &mut (
416                    Option<Box<dyn FnMut(JsObject) -> NapiResult<R>>>,
417                    Option<Box<dyn FnMut(JsObject, T) -> NapiResult<()>>>,
418                ) = std::mem::transmute(data);
419
420                (argc, argv, this.assume_init(), func)
421            };
422
423            let value = T::from_raw(env, argv[0]);
424            let this = JsObject::from_raw(env, this);
425
426            napi_r!(env, func.1.as_mut().unwrap()(this, value))
427        }
428
429        let setter = if let Some(setter) = self.setter {
430            data.1 = Some(setter);
431            Some(setter_trampoline::<T, R> as _)
432        } else {
433            None
434        };
435
436        let attributes = self.attributes.bits();
437
438        let method = None;
439        let value = std::ptr::null_mut();
440
441        Ok(NapiPropertyDescriptor(napi_property_descriptor {
442            utf8name,
443            name,
444            method,
445            getter,
446            setter,
447            value,
448            attributes,
449            data: Box::into_raw(Box::new(data)) as _,
450        }))
451    }
452}
453
454impl Default for DescriptorValueBuilder {
455    fn default() -> Self {
456        Self::new()
457    }
458}
459
460impl<T: FromJsArgs, R: NapiValueT> Default for DescriptorMethodBuilder<T, R> {
461    fn default() -> Self {
462        Self::new()
463    }
464}
465
466impl<T: NapiValueT, R: NapiValueT> Default for DescriptorAccessorBuilder<T, R> {
467    fn default() -> Self {
468        Self::new()
469    }
470}