napi_derive_backend/codegen/
enum.rs1use 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}