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
27pub struct DescriptorValueBuilder {
29 pub utf8name: Option<String>,
30 pub name: napi_value,
31 pub value: napi_value,
32 pub attributes: NapiPropertyAttributes,
33}
34
35#[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#[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 pub fn with_utf8name(mut self, name: impl Into<String>) -> Self {
69 self.utf8name.replace(name.into());
70 self
71 }
72
73 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 pub fn with_value(mut self, value: impl NapiValueT) -> Self {
92 self.value = value.raw();
93 self
94 }
95
96 pub fn with_attribute(mut self, attribute: NapiPropertyAttributes) -> Self {
98 self.attributes |= attribute;
99 self
100 }
101
102 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 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 pub fn with_utf8name(mut self, name: impl Into<String>) -> Self {
152 self.utf8name.replace(name.into());
153 self
154 }
155
156 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 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 pub fn with_attribute(mut self, attribute: NapiPropertyAttributes) -> Self {
184 self.attributes |= attribute;
185 self
186 }
187
188 #[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 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 pub fn with_utf8name(mut self, name: impl Into<String>) -> Self {
286 self.utf8name.replace(name.into());
287 self
288 }
289
290 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 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 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 pub fn with_attribute(mut self, attribute: NapiPropertyAttributes) -> Self {
329 self.attributes |= attribute;
330 self
331 }
332
333 #[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 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 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}