| 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 | |