napi_derive_backend/codegen/
enum.rs

1use proc_macro2::{Ident, Literal, Span, TokenStream};
2use quote::ToTokens;
3
4use crate::{codegen::js_mod_to_token_stream, BindgenResult, NapiEnum, TryToTokens};
5
6impl TryToTokens for NapiEnum {
7  fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()> {
8    let register = self.gen_module_register();
9    let napi_value_conversion = self.gen_napi_value_map_impl();
10
11    (quote! {
12      #napi_value_conversion
13      #register
14    })
15    .to_tokens(tokens);
16
17    Ok(())
18  }
19}
20
21impl NapiEnum {
22  fn gen_napi_value_map_impl(&self) -> TokenStream {
23    let name = &self.name;
24    let name_str = self.name.to_string();
25    let mut from_napi_branches = vec![];
26    let mut to_napi_branches = vec![];
27
28    self.variants.iter().for_each(|v| {
29      let val: Literal = (&v.val).into();
30      let v_name = &v.name;
31
32      from_napi_branches.push(quote! { #val => Ok(#name::#v_name) });
33      to_napi_branches.push(quote! { #name::#v_name => #val });
34    });
35
36    let validate_type = if self.is_string_enum {
37      quote! { napi::bindgen_prelude::ValueType::String }
38    } else {
39      quote! { napi::bindgen_prelude::ValueType::Number }
40    };
41
42    let from_napi_value = if self.variants.is_empty() {
43      quote! {
44        impl napi::bindgen_prelude::FromNapiValue for #name {
45          unsafe fn from_napi_value(
46            env: napi::bindgen_prelude::sys::napi_env,
47            napi_val: napi::bindgen_prelude::sys::napi_value
48          ) -> napi::bindgen_prelude::Result<Self> {
49            Err(napi::bindgen_prelude::error!(
50              napi::bindgen_prelude::Status::InvalidArg,
51              "enum `{}` has no variants",
52              #name_str
53            ))
54          }
55        }
56      }
57    } else {
58      let from_napi_value = if self.is_string_enum {
59        quote! {
60          let val: String = napi::bindgen_prelude::FromNapiValue::from_napi_value(env, napi_val)
61        }
62      } else {
63        quote! {
64          let val = napi::bindgen_prelude::FromNapiValue::from_napi_value(env, napi_val)
65        }
66      };
67      let match_val = if self.is_string_enum {
68        quote! { val.as_str() }
69      } else {
70        quote! { val }
71      };
72      quote! {
73        impl napi::bindgen_prelude::FromNapiValue for #name {
74          unsafe fn from_napi_value(
75            env: napi::bindgen_prelude::sys::napi_env,
76            napi_val: napi::bindgen_prelude::sys::napi_value
77          ) -> napi::bindgen_prelude::Result<Self> {
78            #from_napi_value.map_err(|e| {
79              napi::bindgen_prelude::error!(
80                e.status,
81                "Failed to convert napi value into enum `{}`. {}",
82                #name_str,
83                e,
84              )
85            })?;
86
87            match #match_val {
88              #(#from_napi_branches,)*
89              _ => {
90                Err(napi::bindgen_prelude::error!(
91                  napi::bindgen_prelude::Status::InvalidArg,
92                  "value `{:?}` does not match any variant of enum `{}`",
93                  val,
94                  #name_str
95                ))
96              }
97            }
98          }
99        }
100      }
101    };
102
103    let to_napi_value = if self.variants.is_empty() {
104      quote! {
105        impl napi::bindgen_prelude::ToNapiValue for #name {
106          unsafe fn to_napi_value(
107            env: napi::bindgen_prelude::sys::napi_env,
108            val: Self
109          ) -> napi::bindgen_prelude::Result<napi::bindgen_prelude::sys::napi_value> {
110            napi::bindgen_prelude::ToNapiValue::to_napi_value(env, ())
111          }
112        }
113
114        impl napi::bindgen_prelude::ToNapiValue for &#name {
115          unsafe fn to_napi_value(
116            env: napi::bindgen_prelude::sys::napi_env,
117            val: Self
118          ) -> napi::bindgen_prelude::Result<napi::bindgen_prelude::sys::napi_value> {
119            napi::bindgen_prelude::ToNapiValue::to_napi_value(env, ())
120          }
121        }
122
123        impl napi::bindgen_prelude::ToNapiValue for &mut #name {
124          unsafe fn to_napi_value(
125            env: napi::bindgen_prelude::sys::napi_env,
126            val: Self
127          ) -> napi::bindgen_prelude::Result<napi::bindgen_prelude::sys::napi_value> {
128            napi::bindgen_prelude::ToNapiValue::to_napi_value(env, ())
129          }
130        }
131      }
132    } else {
133      quote! {
134        impl napi::bindgen_prelude::ToNapiValue for #name {
135          unsafe fn to_napi_value(
136            env: napi::bindgen_prelude::sys::napi_env,
137            val: Self
138          ) -> napi::bindgen_prelude::Result<napi::bindgen_prelude::sys::napi_value> {
139            let val = match val {
140              #(#to_napi_branches,)*
141            };
142
143            napi::bindgen_prelude::ToNapiValue::to_napi_value(env, val)
144          }
145        }
146
147        impl napi::bindgen_prelude::ToNapiValue for &#name {
148          unsafe fn to_napi_value(
149            env: napi::bindgen_prelude::sys::napi_env,
150            val: Self
151          ) -> napi::bindgen_prelude::Result<napi::bindgen_prelude::sys::napi_value> {
152            let val = match val {
153              #(#to_napi_branches,)*
154            };
155
156            napi::bindgen_prelude::ToNapiValue::to_napi_value(env, val)
157          }
158        }
159
160        impl napi::bindgen_prelude::ToNapiValue for &mut #name {
161          unsafe fn to_napi_value(
162            env: napi::bindgen_prelude::sys::napi_env,
163            val: Self
164          ) -> napi::bindgen_prelude::Result<napi::bindgen_prelude::sys::napi_value> {
165            let val = match val {
166              #(#to_napi_branches,)*
167            };
168
169            napi::bindgen_prelude::ToNapiValue::to_napi_value(env, val)
170          }
171        }
172      }
173    };
174
175    quote! {
176      impl napi::bindgen_prelude::TypeName for #name {
177        fn type_name() -> &'static str {
178          #name_str
179        }
180
181        fn value_type() -> napi::ValueType {
182          napi::ValueType::Object
183        }
184      }
185
186      impl napi::bindgen_prelude::ValidateNapiValue for #name {
187        unsafe fn validate(
188          env: napi::bindgen_prelude::sys::napi_env,
189          napi_val: napi::bindgen_prelude::sys::napi_value
190        ) -> napi::bindgen_prelude::Result<napi::sys::napi_value> {
191          napi::bindgen_prelude::assert_type_of!(env, napi_val, #validate_type)?;
192          Ok(std::ptr::null_mut())
193        }
194      }
195
196      #from_napi_value
197
198      #to_napi_value
199    }
200  }
201
202  fn gen_module_register(&self) -> TokenStream {
203    let name_str = self.name.to_string();
204    let js_name_lit = Literal::string(&format!("{}\0", &self.js_name));
205    let register_name = &self.register_name;
206
207    let mut define_properties = vec![];
208
209    for variant in self.variants.iter() {
210      let name_lit = Literal::string(&format!("{}\0", variant.name));
211      let val_lit: Literal = (&variant.val).into();
212
213      define_properties.push(quote! {
214        {
215          let name = std::ffi::CStr::from_bytes_with_nul_unchecked(#name_lit.as_bytes());
216          napi::bindgen_prelude::check_status!(
217            napi::bindgen_prelude::sys::napi_set_named_property(
218              env,
219              obj_ptr, name.as_ptr(),
220              napi::bindgen_prelude::ToNapiValue::to_napi_value(env, #val_lit)?
221            ),
222            "Failed to defined enum `{}`",
223            #js_name_lit
224          )?;
225        };
226      })
227    }
228
229    let callback_name = Ident::new(
230      &format!("__register__enum__{}_callback__", name_str),
231      Span::call_site(),
232    );
233
234    let js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref());
235
236    quote! {
237      #[allow(non_snake_case)]
238      #[allow(clippy::all)]
239      unsafe fn #callback_name(env: napi::bindgen_prelude::sys::napi_env) -> napi::bindgen_prelude::Result<napi::bindgen_prelude::sys::napi_value> {
240        use std::ffi::CString;
241        use std::ptr;
242
243        let mut obj_ptr = ptr::null_mut();
244
245        napi::bindgen_prelude::check_status!(
246          napi::bindgen_prelude::sys::napi_create_object(env, &mut obj_ptr),
247          "Failed to create napi object"
248        )?;
249
250        #(#define_properties)*
251
252        Ok(obj_ptr)
253      }
254      #[allow(non_snake_case)]
255      #[allow(clippy::all)]
256      #[cfg(all(not(test), not(target_family = "wasm")))]
257      #[napi::ctor::ctor(crate_path=napi::ctor)]
258      fn #register_name() {
259        napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name_lit, #callback_name);
260      }
261      #[allow(non_snake_case)]
262      #[allow(clippy::all)]
263      #[cfg(all(not(test), target_family = "wasm"))]
264      #[no_mangle]
265      extern "C" fn #register_name() {
266        napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name_lit, #callback_name);
267      }
268    }
269  }
270}