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
37      .variants
38      .iter()
39      .any(|v| matches!(v.val, crate::NapiEnumValue::String(_)))
40    {
41      quote! { napi::bindgen_prelude::ValueType::String }
42    } else {
43      quote! { napi::bindgen_prelude::ValueType::Number }
44    };
45
46    let from_napi_value = if self.variants.is_empty() {
47      quote! {
48        impl napi::bindgen_prelude::FromNapiValue for #name {
49          unsafe fn from_napi_value(
50            env: napi::bindgen_prelude::sys::napi_env,
51            napi_val: napi::bindgen_prelude::sys::napi_value
52          ) -> napi::bindgen_prelude::Result<Self> {
53            Err(napi::bindgen_prelude::error!(
54              napi::bindgen_prelude::Status::InvalidArg,
55              "enum `{}` has no variants",
56              #name_str
57            ))
58          }
59        }
60      }
61    } else {
62      quote! {
63        impl napi::bindgen_prelude::FromNapiValue for #name {
64          unsafe fn from_napi_value(
65            env: napi::bindgen_prelude::sys::napi_env,
66            napi_val: napi::bindgen_prelude::sys::napi_value
67          ) -> napi::bindgen_prelude::Result<Self> {
68            let val = napi::bindgen_prelude::FromNapiValue::from_napi_value(env, napi_val).map_err(|e| {
69              napi::bindgen_prelude::error!(
70                e.status,
71                "Failed to convert napi value into enum `{}`. {}",
72                #name_str,
73                e,
74              )
75            })?;
76
77            match val {
78              #(#from_napi_branches,)*
79              _ => {
80                Err(napi::bindgen_prelude::error!(
81                  napi::bindgen_prelude::Status::InvalidArg,
82                  "value `{:?}` does not match any variant of enum `{}`",
83                  val,
84                  #name_str
85                ))
86              }
87            }
88          }
89        }
90      }
91    };
92
93    let to_napi_value = if self.variants.is_empty() {
94      quote! {
95        impl napi::bindgen_prelude::ToNapiValue for #name {
96          unsafe fn to_napi_value(
97            env: napi::bindgen_prelude::sys::napi_env,
98            val: Self
99          ) -> napi::bindgen_prelude::Result<napi::bindgen_prelude::sys::napi_value> {
100            napi::bindgen_prelude::ToNapiValue::to_napi_value(env, ())
101          }
102        }
103      }
104    } else {
105      quote! {
106        impl napi::bindgen_prelude::ToNapiValue for #name {
107          unsafe fn to_napi_value(
108            env: napi::bindgen_prelude::sys::napi_env,
109            val: Self
110          ) -> napi::bindgen_prelude::Result<napi::bindgen_prelude::sys::napi_value> {
111            let val = match val {
112              #(#to_napi_branches,)*
113            };
114
115            napi::bindgen_prelude::ToNapiValue::to_napi_value(env, val)
116          }
117        }
118      }
119    };
120
121    quote! {
122      impl napi::bindgen_prelude::TypeName for #name {
123        fn type_name() -> &'static str {
124          #name_str
125        }
126
127        fn value_type() -> napi::ValueType {
128          napi::ValueType::Object
129        }
130      }
131
132      impl napi::bindgen_prelude::ValidateNapiValue for #name {
133        unsafe fn validate(
134          env: napi::bindgen_prelude::sys::napi_env,
135          napi_val: napi::bindgen_prelude::sys::napi_value
136        ) -> napi::bindgen_prelude::Result<napi::sys::napi_value> {
137          napi::bindgen_prelude::assert_type_of!(env, napi_val, #validate_type)?;
138          Ok(std::ptr::null_mut())
139        }
140      }
141
142      #from_napi_value
143
144      #to_napi_value
145    }
146  }
147
148  fn gen_module_register(&self) -> TokenStream {
149    let name_str = self.name.to_string();
150    let js_name_lit = Literal::string(&format!("{}\0", &self.js_name));
151    let register_name = &self.register_name;
152
153    let mut define_properties = vec![];
154
155    for variant in self.variants.iter() {
156      let name_lit = Literal::string(&format!("{}\0", variant.name));
157      let val_lit: Literal = (&variant.val).into();
158
159      define_properties.push(quote! {
160        {
161          let name = std::ffi::CStr::from_bytes_with_nul_unchecked(#name_lit.as_bytes());
162          napi::bindgen_prelude::check_status!(
163            napi::bindgen_prelude::sys::napi_set_named_property(
164              env,
165              obj_ptr, name.as_ptr(),
166              napi::bindgen_prelude::ToNapiValue::to_napi_value(env, #val_lit)?
167            ),
168            "Failed to defined enum `{}`",
169            #js_name_lit
170          )?;
171        };
172      })
173    }
174
175    let callback_name = Ident::new(
176      &format!("__register__enum__{}_callback__", name_str),
177      Span::call_site(),
178    );
179
180    let js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref());
181
182    quote! {
183      #[allow(non_snake_case)]
184      #[allow(clippy::all)]
185      unsafe fn #callback_name(env: napi::bindgen_prelude::sys::napi_env) -> napi::bindgen_prelude::Result<napi::bindgen_prelude::sys::napi_value> {
186        use std::ffi::CString;
187        use std::ptr;
188
189        let mut obj_ptr = ptr::null_mut();
190
191        napi::bindgen_prelude::check_status!(
192          napi::bindgen_prelude::sys::napi_create_object(env, &mut obj_ptr),
193          "Failed to create napi object"
194        )?;
195
196        #(#define_properties)*
197
198        Ok(obj_ptr)
199      }
200      #[allow(non_snake_case)]
201      #[allow(clippy::all)]
202      #[cfg(all(not(test), not(target_family = "wasm")))]
203      #[napi::bindgen_prelude::ctor]
204      fn #register_name() {
205        napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name_lit, #callback_name);
206      }
207      #[allow(non_snake_case)]
208      #[allow(clippy::all)]
209      #[cfg(all(not(test), target_family = "wasm"))]
210      #[no_mangle]
211      extern "C" fn #register_name() {
212        napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name_lit, #callback_name);
213      }
214    }
215  }
216}