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