1 | use proc_macro2::{Ident, Literal, Span, TokenStream}; |
2 | use quote::ToTokens; |
3 | |
4 | use crate::{codegen::js_mod_to_token_stream, BindgenResult, NapiEnum, TryToTokens}; |
5 | |
6 | impl TryToTokens for NapiEnum { |
7 | fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()> { |
8 | let register: TokenStream = self.gen_module_register(); |
9 | let napi_value_conversion: TokenStream = 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 | |
21 | impl 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 | } |
217 | |