diff --git a/cli/src/build.ts b/cli/src/build.ts index e1732f40..00d3388b 100644 --- a/cli/src/build.ts +++ b/cli/src/build.ts @@ -219,7 +219,7 @@ async function findUp(dir = process.cwd()): Promise { } interface TypeDef { - kind: 'fn' | 'struct' | 'impl' | 'enum' + kind: 'fn' | 'struct' | 'impl' | 'enum' | 'interface' name: string def: string } @@ -243,34 +243,27 @@ async function processIntermediateTypeFile(source: string, target: string) { const def = JSON.parse(line) as TypeDef switch (def.kind) { - case 'fn': - case 'enum': - dts += def.def + '\n' - break case 'struct': classes.set(def.name, def.def) break case 'impl': impls.set(def.name, def.def) + break + case 'interface': + dts += `interface ${def.name} {\n${indentLines(def.def, 2)}\n}\n` + break + default: + dts += def.def + '\n' } }) for (const [name, classDef] of classes.entries()) { const implDef = impls.get(name) - dts += `export class ${name} { - ${classDef - .split('\n') - .map((line) => line.trim()) - .join('\n ')}` + dts += `export class ${name} {\n${indentLines(classDef, 2)}` if (implDef) { - dts += - '\n ' + - implDef - .split('\n') - .map((line) => line.trim()) - .join('\n ') + dts += `\n${indentLines(implDef, 2)}` } dts += '\n}\n' @@ -279,3 +272,10 @@ async function processIntermediateTypeFile(source: string, target: string) { await unlinkAsync(source) await writeFileAsync(target, dts, 'utf8') } + +function indentLines(input: string, spaces: number) { + return input + .split('\n') + .map((line) => ''.padEnd(spaces, ' ') + line.trim()) + .join('\n') +} diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs index 6c71303c..2b4dfa93 100644 --- a/crates/backend/src/ast.rs +++ b/crates/backend/src/ast.rs @@ -51,7 +51,14 @@ pub struct NapiStruct { pub vis: syn::Visibility, pub fields: Vec, pub is_tuple: bool, - pub gen_default_ctor: bool, + pub kind: NapiStructKind, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum NapiStructKind { + None, + Constructor, + Object, } #[derive(Debug, Clone)] diff --git a/crates/backend/src/codegen/struct.rs b/crates/backend/src/codegen/struct.rs index 13d316c4..886feb8a 100644 --- a/crates/backend/src/codegen/struct.rs +++ b/crates/backend/src/codegen/struct.rs @@ -5,7 +5,7 @@ use quote::ToTokens; use crate::{ codegen::{get_intermediate_ident, get_register_ident}, - BindgenResult, FnKind, NapiImpl, NapiStruct, TryToTokens, + BindgenResult, FnKind, NapiImpl, NapiStruct, NapiStructKind, TryToTokens, }; // Generate trait implementations for given Struct. @@ -53,7 +53,12 @@ fn gen_napi_value_map_impl(name: &Ident, to_napi_val_impl: TokenStream) -> Token impl TryToTokens for NapiStruct { fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()> { let napi_value_map_impl = self.gen_napi_value_map_impl(); - let class_helper_mod = self.gen_helper_mod(); + + let class_helper_mod = if self.kind == NapiStructKind::Object { + quote! {} + } else { + self.gen_helper_mod() + }; (quote! { #napi_value_map_impl @@ -72,7 +77,7 @@ impl NapiStruct { Span::call_site(), ); - let ctor = if self.gen_default_ctor { + let ctor = if self.kind == NapiStructKind::Constructor { self.gen_default_ctor() } else { quote! {} @@ -134,14 +139,20 @@ impl NapiStruct { } fn gen_napi_value_map_impl(&self) -> TokenStream { - if !self.gen_default_ctor { - return gen_napi_value_map_impl(&self.name, quote! {}); + match self.kind { + NapiStructKind::None => gen_napi_value_map_impl(&self.name, quote! {}), + NapiStructKind::Constructor => { + gen_napi_value_map_impl(&self.name, self.gen_to_napi_value_ctor_impl()) + } + NapiStructKind::Object => self.gen_to_napi_value_obj_impl(), } + } + fn gen_to_napi_value_ctor_impl(&self) -> TokenStream { let name = &self.name; let js_name_str = &self.js_name; - let mut fields_conversions = vec![]; + let mut field_conversions = vec![]; let mut field_destructions = vec![]; for field in self.fields.iter() { @@ -150,57 +161,124 @@ impl NapiStruct { match &field.name { syn::Member::Named(ident) => { field_destructions.push(quote! { #ident }); - fields_conversions.push(quote! { <#ty as ToNapiValue>::to_napi_value(env, #ident)? }); + field_conversions.push(quote! { <#ty as ToNapiValue>::to_napi_value(env, #ident)? }); } syn::Member::Unnamed(i) => { field_destructions.push(quote! { arg#i }); - fields_conversions.push(quote! { <#ty as ToNapiValue>::to_napi_value(env, arg#i)? }); + field_conversions.push(quote! { <#ty as ToNapiValue>::to_napi_value(env, arg#i)? }); } } } let destructed_fields = if self.is_tuple { quote! { - let Self (#(#field_destructions),*) + Self (#(#field_destructions),*) } } else { quote! { - let Self {#(#field_destructions),*} + Self {#(#field_destructions),*} } }; - gen_napi_value_map_impl( - name, - quote! { - impl ToNapiValue for #name { - unsafe fn to_napi_value(env: sys::napi_env, val: #name) -> Result { - if let Some(ctor_ref) = get_class_constructor(#js_name_str) { - let mut ctor = std::ptr::null_mut(); + quote! { + impl ToNapiValue for #name { + unsafe fn to_napi_value(env: sys::napi_env, val: #name) -> Result { + if let Some(ctor_ref) = get_class_constructor(#js_name_str) { + let mut ctor = std::ptr::null_mut(); - check_status!( - sys::napi_get_reference_value(env, ctor_ref, &mut ctor), - "Failed to get constructor of class `{}`", - #js_name_str - )?; + check_status!( + sys::napi_get_reference_value(env, ctor_ref, &mut ctor), + "Failed to get constructor of class `{}`", + #js_name_str + )?; - let mut result = std::ptr::null_mut(); - #destructed_fields = val; - let args = vec![#(#fields_conversions),*]; + let mut result = std::ptr::null_mut(); + let #destructed_fields = val; + let args = vec![#(#field_conversions),*]; - check_status!( - sys::napi_new_instance(env, ctor, args.len(), args.as_ptr(), &mut result), - "Failed to construct class `{}`", - #js_name_str - )?; + check_status!( + sys::napi_new_instance(env, ctor, args.len(), args.as_ptr(), &mut result), + "Failed to construct class `{}`", + #js_name_str + )?; - Ok(result) - } else { - Err(Error::new(Status::InvalidArg, format!("Failed to get constructor of class `{}`", #js_name_str))) - } + Ok(result) + } else { + Err(Error::new(Status::InvalidArg, format!("Failed to get constructor of class `{}`", #js_name_str))) } } - }, - ) + } + } + } + + fn gen_to_napi_value_obj_impl(&self) -> TokenStream { + let name = &self.name; + let name_str = self.name.to_string(); + + let mut obj_field_setters = vec![]; + let mut obj_field_getters = vec![]; + let mut field_destructions = vec![]; + + for field in self.fields.iter() { + let field_js_name = &field.js_name; + let ty = &field.ty; + + match &field.name { + syn::Member::Named(ident) => { + field_destructions.push(quote! { #ident }); + obj_field_setters.push(quote! { obj.set(#field_js_name, #ident)?; }); + obj_field_getters.push(quote! { let #ident: #ty = obj.get(#field_js_name)?.expect(&format!("Field {} should exist", #field_js_name)); }); + } + syn::Member::Unnamed(i) => { + field_destructions.push(quote! { arg#i }); + obj_field_setters.push(quote! { obj.set(#field_js_name, arg#1)?; }); + obj_field_getters.push(quote! { let arg#i: #ty = obj.get(#field_js_name)?.expect(&format!("Field {} should exist", #field_js_name)); }); + } + } + } + + let destructed_fields = if self.is_tuple { + quote! { + Self (#(#field_destructions),*) + } + } else { + quote! { + Self {#(#field_destructions),*} + } + }; + + quote! { + impl TypeName for #name { + fn type_name() -> &'static str { + #name_str + } + } + + impl ToNapiValue for #name { + unsafe fn to_napi_value(env: sys::napi_env, val: #name) -> Result { + let env_wrapper = Env::from(env); + let mut obj = env_wrapper.create_object()?; + + let #destructed_fields = val; + #(#obj_field_setters)* + + Object::to_napi_value(env, obj) + } + } + + impl FromNapiValue for #name { + unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + let env_wrapper = Env::from(env); + let mut obj = Object::from_napi_value(env, napi_val)?; + + #(#obj_field_getters)* + + let val = #destructed_fields; + + Ok(val) + } + } + } } fn gen_default_getters_setters(&self) -> Vec { @@ -273,7 +351,7 @@ impl NapiStruct { let js_name = &self.js_name; let mut props = vec![]; - if self.gen_default_ctor { + if self.kind == NapiStructKind::Constructor { props.push(quote! { Property::new("constructor").unwrap().with_ctor(constructor) }); } diff --git a/crates/backend/src/typegen.rs b/crates/backend/src/typegen.rs index 7dd173f0..33ebe7c3 100644 --- a/crates/backend/src/typegen.rs +++ b/crates/backend/src/typegen.rs @@ -5,8 +5,6 @@ mod r#struct; use std::collections::HashMap; use once_cell::sync::Lazy; -use quote::ToTokens; -use regex::Regex; use syn::Type; #[derive(Default)] @@ -29,84 +27,112 @@ pub trait ToTypeDef { fn to_type_def(&self) -> TypeDef; } -pub static TYPE_REGEXES: Lazy> = Lazy::new(|| { +static KNOWN_TYPES: Lazy> = Lazy::new(|| { let mut map = HashMap::default(); map.extend([ - ("Vec", Regex::new(r"^Vec < (.*) >$").unwrap()), - ("Option", Regex::new(r"^Option < (.*) >").unwrap()), - ("Result", Regex::new(r"^Result < (.*) >").unwrap()), - ("HashMap", Regex::new(r"HashMap < (.*), (.*) >").unwrap()), + ("()", "undefined"), + ("i8", "number"), + ("i16", "number"), + ("i32", "number"), + ("i64", "number"), + ("u8", "number"), + ("u16", "number"), + ("u32", "number"), + ("u64", "BigInt"), + ("u128", "BigInt"), + ("i128", "BigInt"), + ("usize", "BigInt"), + ("isize", "BigInt"), + ("BigInt", "BigInt"), + ("bool", "boolean"), + ("String", "string"), + ("str", "string"), + ("Latin1String", "string"), + ("Utf16String", "string"), + ("char", "string"), + ("Object", "object"), + ("Value", "any"), + ("Map", "Record"), + ("HashMap", "Record<{}, {}>"), + ("Vec", "Array<{}>"), + ("Option", "{} | null"), + ("Result", "Error | {}"), ]); map }); -pub fn ty_to_ts_type(ty: &Type, omit_top_level_result: bool) -> String { +fn fill_ty(template: &str, args: Vec) -> String { + let matches = template.match_indices("{}").collect::>(); + if args.len() != matches.len() { + return String::from("any"); + } + + let mut ret = String::from(""); + let mut prev = 0; + matches.into_iter().zip(args).for_each(|((index, _), arg)| { + ret.push_str(&template[prev..index]); + ret.push_str(&arg); + prev = index + 2; + }); + + ret.push_str(&template[prev..]); + ret +} + +pub fn ty_to_ts_type(ty: &Type, is_return_ty: bool) -> String { match ty { - Type::Reference(r) => ty_to_ts_type(&r.elem, omit_top_level_result), + Type::Reference(r) => ty_to_ts_type(&r.elem, is_return_ty), Type::Tuple(tuple) => { - format!( - "[{}]", - tuple - .elems - .iter() - .map(|elem| ty_to_ts_type(elem, false)) - .collect::>() - .join(", ") - ) + if tuple.elems.is_empty() { + "undefined".to_owned() + } else { + format!( + "[{}]", + tuple + .elems + .iter() + .map(|elem| ty_to_ts_type(elem, false)) + .collect::>() + .join(", ") + ) + } + } + Type::Path(syn::TypePath { qself: None, path }) => { + let mut ts_ty = None; + + if let Some(syn::PathSegment { ident, arguments }) = path.segments.last() { + let rust_ty = ident.to_string(); + let args = if let syn::PathArguments::AngleBracketed(arguments) = arguments { + arguments + .args + .iter() + .filter_map(|arg| match arg { + syn::GenericArgument::Type(generic_ty) => Some(ty_to_ts_type(generic_ty, false)), + _ => None, + }) + .collect::>() + } else { + vec![] + }; + + if rust_ty == "Result" && is_return_ty { + ts_ty = Some(args.first().unwrap().to_owned()); + } else if let Some(&known_ty) = KNOWN_TYPES.get(rust_ty.as_str()) { + if known_ty.contains("{}") { + ts_ty = Some(fill_ty(known_ty, args)); + } else { + ts_ty = Some(known_ty.to_owned()); + } + } else { + // there should be runtime registered type in else + ts_ty = Some(rust_ty); + } + } + + ts_ty.unwrap_or_else(|| "any".to_owned()) } - Type::Path(syn::TypePath { qself: None, path }) => str_to_ts_type( - path.to_token_stream().to_string().as_str(), - omit_top_level_result, - ), _ => "any".to_owned(), } } - -pub fn str_to_ts_type(ty: &str, omit_top_level_result: bool) -> String { - match ty { - "()" => "undefined".to_owned(), - "i8" | "i16" | "i32" | "i64" | "u8" | "u16" | "u32" => "number".to_owned(), - "i128" | "isize" | "u64" | "u128" | "usize" => "BigInt".to_owned(), - "bool" => "boolean".to_owned(), - "String" | "str" | "char" => "string".to_owned(), - "Object" => "object".to_owned(), - // nothing but `& 'lifetime str` could ends with ` str` - s if s.ends_with(" str") => "string".to_owned(), - s if s.starts_with("Vec") && TYPE_REGEXES["Vec"].is_match(s) => { - let captures = TYPE_REGEXES["Vec"].captures(s).unwrap(); - let inner = captures.get(1).unwrap().as_str(); - - format!("Array<{}>", str_to_ts_type(inner, false)) - } - s if s.starts_with("Option") && TYPE_REGEXES["Option"].is_match(s) => { - let captures = TYPE_REGEXES["Option"].captures(s).unwrap(); - let inner = captures.get(1).unwrap().as_str(); - - format!("{} | null", str_to_ts_type(inner, false)) - } - s if s.starts_with("Result") && TYPE_REGEXES["Result"].is_match(s) => { - let captures = TYPE_REGEXES["Result"].captures(s).unwrap(); - let inner = captures.get(1).unwrap().as_str(); - - if omit_top_level_result { - str_to_ts_type(inner, false) - } else { - format!("Error | {}", str_to_ts_type(inner, false)) - } - } - s if TYPE_REGEXES["HashMap"].is_match(s) => { - let captures = TYPE_REGEXES["HashMap"].captures(s).unwrap(); - let key = captures.get(1).unwrap().as_str(); - let val = captures.get(2).unwrap().as_str(); - - format!( - "Record<{}, {}>", - str_to_ts_type(key, false), - str_to_ts_type(val, false) - ) - } - s => s.to_owned(), - } -} diff --git a/crates/backend/src/typegen/struct.rs b/crates/backend/src/typegen/struct.rs index e20117cb..9943691c 100644 --- a/crates/backend/src/typegen/struct.rs +++ b/crates/backend/src/typegen/struct.rs @@ -1,12 +1,16 @@ use super::{ToTypeDef, TypeDef}; -use crate::{ty_to_ts_type, NapiImpl, NapiStruct}; +use crate::{ty_to_ts_type, NapiImpl, NapiStruct, NapiStructKind}; impl ToTypeDef for NapiStruct { fn to_type_def(&self) -> TypeDef { TypeDef { - kind: "struct".to_owned(), + kind: String::from(if self.kind == NapiStructKind::Object { + "interface" + } else { + "struct" + }), name: self.js_name.to_owned(), - def: self.gen_ts_class_fields(), + def: self.gen_ts_class(), } } } @@ -27,7 +31,7 @@ impl ToTypeDef for NapiImpl { } impl NapiStruct { - fn gen_ts_class_fields(&self) -> String { + fn gen_ts_class(&self) -> String { let mut ctor_args = vec![]; let def = self .fields @@ -40,7 +44,7 @@ impl NapiStruct { field_str.push_str("readonly ") } let arg = format!("{}: {}", &f.js_name, ty_to_ts_type(&f.ty, false)); - if self.gen_default_ctor { + if self.kind == NapiStructKind::Constructor { ctor_args.push(arg.clone()); } field_str.push_str(&arg); @@ -50,7 +54,7 @@ impl NapiStruct { .collect::>() .join("\\n"); - if self.gen_default_ctor { + if self.kind == NapiStructKind::Constructor { format!("{}\\nconstructor({})", def, ctor_args.join(", ")) } else { def diff --git a/crates/macro/src/parser/attrs.rs b/crates/macro/src/parser/attrs.rs index c234b281..50723c58 100644 --- a/crates/macro/src/parser/attrs.rs +++ b/crates/macro/src/parser/attrs.rs @@ -49,6 +49,7 @@ macro_rules! attrgen { (readonly, Readonly(Span)), (skip, Skip(Span)), (strict, Strict(Span)), + (object, Object(Span)), // impl later // (inspectable, Inspectable(Span)), diff --git a/crates/macro/src/parser/mod.rs b/crates/macro/src/parser/mod.rs index 712a5a29..1dccac7d 100644 --- a/crates/macro/src/parser/mod.rs +++ b/crates/macro/src/parser/mod.rs @@ -10,7 +10,7 @@ use attrs::{BindgenAttr, BindgenAttrs}; use convert_case::{Case, Casing}; use napi_derive_backend::{ BindgenResult, CallbackArg, Diagnostic, FnKind, FnSelf, Napi, NapiEnum, NapiEnumVariant, NapiFn, - NapiFnArgKind, NapiImpl, NapiItem, NapiStruct, NapiStructField, + NapiFnArgKind, NapiImpl, NapiItem, NapiStruct, NapiStructField, NapiStructKind, }; use proc_macro2::{Ident, TokenStream, TokenTree}; use quote::ToTokens; @@ -665,16 +665,22 @@ impl ConvertToAST for syn::ItemStruct { ); let mut fields = vec![]; let mut is_tuple = false; - let gen_default_ctor = opts.constructor().is_some(); + let struct_kind = if opts.constructor().is_some() { + NapiStructKind::Constructor + } else if opts.object().is_some() { + NapiStructKind::Object + } else { + NapiStructKind::None + }; for (i, field) in self.fields.iter_mut().enumerate() { match field.vis { syn::Visibility::Public(..) => {} _ => { - if gen_default_ctor { + if struct_kind != NapiStructKind::None { errors.push(err_span!( field, - "#[napi] requires all struct fields to be public to mark struct as constructor\nthis field is not public." + "#[napi] requires all struct fields to be public to mark struct as constructor or object shape\nthis field is not public." )); } continue; @@ -693,7 +699,7 @@ impl ConvertToAST for syn::ItemStruct { ), None => { is_tuple = true; - (i.to_string(), syn::Member::Unnamed(i.into())) + (format!("field{}", i), syn::Member::Unnamed(i.into())) } }; @@ -719,7 +725,7 @@ impl ConvertToAST for syn::ItemStruct { vis, fields, is_tuple, - gen_default_ctor, + kind: struct_kind, }), }) } diff --git a/crates/napi/src/bindgen_runtime/callback_info.rs b/crates/napi/src/bindgen_runtime/callback_info.rs index e85824ae..a7726bea 100644 --- a/crates/napi/src/bindgen_runtime/callback_info.rs +++ b/crates/napi/src/bindgen_runtime/callback_info.rs @@ -56,7 +56,6 @@ impl CallbackInfo { pub fn construct(&self, js_name: &str, obj: T) -> Result { let obj = Box::new(obj); - let mut result = std::ptr::null_mut(); let this = self.this(); unsafe { @@ -67,7 +66,7 @@ impl CallbackInfo { Box::into_raw(obj) as *mut std::ffi::c_void, Some(raw_finalize_unchecked::), ptr::null_mut(), - &mut result + &mut std::ptr::null_mut() ), "Failed to initialize class `{}`", js_name, diff --git a/crates/napi/src/bindgen_runtime/js_values.rs b/crates/napi/src/bindgen_runtime/js_values.rs index ee7479db..a2d2301c 100644 --- a/crates/napi/src/bindgen_runtime/js_values.rs +++ b/crates/napi/src/bindgen_runtime/js_values.rs @@ -8,6 +8,8 @@ mod map; mod nil; mod number; mod object; +#[cfg(feature = "serde-json")] +mod serde; mod string; pub use array::*; diff --git a/crates/napi/src/bindgen_runtime/js_values/object.rs b/crates/napi/src/bindgen_runtime/js_values/object.rs index 90f62fb6..2d08c3af 100644 --- a/crates/napi/src/bindgen_runtime/js_values/object.rs +++ b/crates/napi/src/bindgen_runtime/js_values/object.rs @@ -2,8 +2,8 @@ use crate::{bindgen_prelude::*, check_status, sys, type_of, ValueType}; use std::{ffi::CString, ptr}; pub struct Object { - env: sys::napi_env, - inner: sys::napi_value, + pub(crate) env: sys::napi_env, + pub(crate) inner: sys::napi_value, } impl Object { diff --git a/crates/napi/src/bindgen_runtime/js_values/serde.rs b/crates/napi/src/bindgen_runtime/js_values/serde.rs new file mode 100644 index 00000000..988c239f --- /dev/null +++ b/crates/napi/src/bindgen_runtime/js_values/serde.rs @@ -0,0 +1,95 @@ +use serde_json::{Map, Value}; + +use crate::{bindgen_runtime::Null, check_status, sys, type_of, Error, Result, Status, ValueType}; + +use super::{FromNapiValue, Object, ToNapiValue}; + +impl ToNapiValue for Value { + unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { + match val { + Value::Null => Null::to_napi_value(env, Null), + Value::Bool(b) => bool::to_napi_value(env, b), + Value::Number(n) => { + if n.is_i64() { + i64::to_napi_value(env, n.as_i64().unwrap()) + } else if n.is_f64() { + f64::to_napi_value(env, n.as_f64().unwrap()) + } else { + let n = n.as_u64().unwrap(); + if n > u32::MAX as u64 { + todo!("impl BigInt") + } else { + u32::to_napi_value(env, n as u32) + } + } + } + Value::String(s) => String::to_napi_value(env, s), + Value::Array(arr) => Vec::::to_napi_value(env, arr), + Value::Object(obj) => Map::to_napi_value(env, obj), + } + } +} + +impl FromNapiValue for Value { + unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + let ty = type_of!(env, napi_val)?; + let val = match ty { + ValueType::Boolean => Value::Bool(bool::from_napi_value(env, napi_val)?), + ValueType::Number => { + return Err(Error::new( + Status::InvalidArg, + "Js Number is not be able to convert to rust.".to_owned(), + )); + } + ValueType::String => Value::String(String::from_napi_value(env, napi_val)?), + ValueType::Object => { + let mut is_arr = false; + check_status!( + sys::napi_is_array(env, napi_val, &mut is_arr), + "Failed to detect whether given js is an array" + )?; + + if is_arr { + Value::Array(Vec::::from_napi_value(env, napi_val)?) + } else { + Value::Object(Map::::from_napi_value(env, napi_val)?) + } + } + #[cfg(feature = "napi6")] + ValueType::Bigint => todo!(), + _ => Value::Null, + }; + + Ok(val) + } +} + +impl ToNapiValue for Map { + unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result { + let mut obj = Object::new(env)?; + + for (k, v) in val.into_iter() { + obj.set(k, v)?; + } + + Object::to_napi_value(env, obj) + } +} + +impl FromNapiValue for Map { + unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + let obj = Object { + env, + inner: napi_val, + }; + + let mut map = Map::new(); + for key in Object::keys(&obj)?.into_iter() { + if let Some(val) = obj.get(&key)? { + map.insert(key, val); + } + } + + Ok(map) + } +} diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index b2dd10ea..c4386c50 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -13,7 +13,6 @@ Generated by [AVA](https://avajs.dev). export function sumNums(nums: Array): number␊ export function getCwd(callback: (arg0: string) => void): void␊ export function readFile(callback: (arg0: Error | undefined, arg1: string | null) => void): void␊ - export function readPackageJson(): PackageJson␊ export enum Kind { Dog = 0, Cat = 1, Duck = 2 }␊ export enum CustomNumEnum { One = 1, Two = 2, Three = 3, Four = 4, Six = 6, Eight = 8, Nine = 9, Ten = 10 }␊ export function enumToI32(e: CustomNumEnum): number␊ @@ -23,10 +22,18 @@ Generated by [AVA](https://avajs.dev). export function fibonacci(n: number): number␊ export function listObjKeys(obj: object): Array␊ export function createObj(): object␊ + interface PackageJson {␊ + name: string␊ + version: string␊ + dependencies: Record | null␊ + devDependencies: Record | null␊ + }␊ + export function readPackageJson(): PackageJson␊ + export function getPackageJsonName(packageJson: PackageJson): string␊ export function contains(source: string, target: string): boolean␊ export function concatStr(mutS: string): string␊ - export function concatUtf16(s: Utf16String): Utf16String␊ - export function concatLatin1(s: Latin1String): string␊ + export function concatUtf16(s: string): string␊ + export function concatLatin1(s: string): string␊ export class Animal {␊ readonly kind: Kind␊ constructor(kind: Kind, name: string)␊ @@ -35,11 +42,4 @@ Generated by [AVA](https://avajs.dev). whoami(): string␊ static getDogKind(): Kind␊ }␊ - export class PackageJson {␊ - name: string␊ - version: string␊ - dependencies: Record | null␊ - devDependencies: Record | null␊ - constructor(name: string, version: string, dependencies: Record | null, devDependencies: Record | null)␊ - }␊ ` diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index cf99096f..6a4ff45d 100644 Binary files a/examples/napi/__test__/typegen.spec.ts.snap and b/examples/napi/__test__/typegen.spec.ts.snap differ diff --git a/examples/napi/__test__/values.spec.ts b/examples/napi/__test__/values.spec.ts index 643e74e7..f8dfdef9 100644 --- a/examples/napi/__test__/values.spec.ts +++ b/examples/napi/__test__/values.spec.ts @@ -21,6 +21,7 @@ import { readFile, throwError, readPackageJson, + getPackageJsonName, } from '../' test('number', (t) => { @@ -68,12 +69,6 @@ test('class', (t) => { dog.name = '可乐' t.is(dog.name, '可乐') - - const packageJson = readPackageJson() - t.is(packageJson.name, 'napi-rs') - t.is(packageJson.version, '0.0.0') - t.is(packageJson.dependencies, null) - t.snapshot(Object.keys(packageJson.devDependencies!).sort()) }) test('callback', (t) => { @@ -107,3 +102,13 @@ test('Option', (t) => { test('Result', (t) => { t.throws(() => throwError(), null, 'Manual Error') }) + +test('serde-json', (t) => { + const packageJson = readPackageJson() + t.is(packageJson.name, 'napi-rs') + t.is(packageJson.version, '0.0.0') + t.is(packageJson.dependencies, null) + t.snapshot(Object.keys(packageJson.devDependencies!).sort()) + + t.is(getPackageJsonName(packageJson), 'napi-rs') +}) diff --git a/examples/napi/__test__/values.spec.ts.md b/examples/napi/__test__/values.spec.ts.md index a33cc770..1400a0f4 100644 --- a/examples/napi/__test__/values.spec.ts.md +++ b/examples/napi/__test__/values.spec.ts.md @@ -4,7 +4,7 @@ The actual snapshot is saved in `values.spec.ts.snap`. Generated by [AVA](https://avajs.dev). -## class +## serde-json > Snapshot 1 diff --git a/examples/napi/__test__/values.spec.ts.snap b/examples/napi/__test__/values.spec.ts.snap index 3004ebd5..97d15dd7 100644 Binary files a/examples/napi/__test__/values.spec.ts.snap and b/examples/napi/__test__/values.spec.ts.snap differ diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index 655b8355..0fcfc6d5 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -3,7 +3,6 @@ export function getNums(): Array export function sumNums(nums: Array): number export function getCwd(callback: (arg0: string) => void): void export function readFile(callback: (arg0: Error | undefined, arg1: string | null) => void): void -export function readPackageJson(): PackageJson export enum Kind { Dog = 0, Cat = 1, Duck = 2 } export enum CustomNumEnum { One = 1, Two = 2, Three = 3, Four = 4, Six = 6, Eight = 8, Nine = 9, Ten = 10 } export function enumToI32(e: CustomNumEnum): number @@ -13,10 +12,18 @@ export function add(a: number, b: number): number export function fibonacci(n: number): number export function listObjKeys(obj: object): Array export function createObj(): object +interface PackageJson { + name: string + version: string + dependencies: Record | null + devDependencies: Record | null +} +export function readPackageJson(): PackageJson +export function getPackageJsonName(packageJson: PackageJson): string export function contains(source: string, target: string): boolean export function concatStr(mutS: string): string -export function concatUtf16(s: Utf16String): Utf16String -export function concatLatin1(s: Latin1String): string +export function concatUtf16(s: string): string +export function concatLatin1(s: string): string export class Animal { readonly kind: Kind constructor(kind: Kind, name: string) @@ -25,10 +32,3 @@ export class Animal { whoami(): string static getDogKind(): Kind } -export class PackageJson { - name: string - version: string - dependencies: Record | null - devDependencies: Record | null - constructor(name: string, version: string, dependencies: Record | null, devDependencies: Record | null) -} diff --git a/examples/napi/src/class.rs b/examples/napi/src/class.rs index 73321425..444de5ba 100644 --- a/examples/napi/src/class.rs +++ b/examples/napi/src/class.rs @@ -1,5 +1,3 @@ -use std::{collections::HashMap, fs}; - use napi::bindgen_prelude::*; use crate::r#enum::Kind; @@ -47,20 +45,3 @@ impl Animal { Kind::Dog } } - -#[napi(constructor)] -#[derive(Serialize, Deserialize, Debug)] -struct PackageJson { - pub name: String, - pub version: String, - pub dependencies: Option>, - #[serde(rename = "devDependencies")] - pub dev_dependencies: Option>, -} - -#[napi] -fn read_package_json() -> Result { - let raw = fs::read_to_string("package.json")?; - let p: PackageJson = serde_json::from_str(&raw)?; - Ok(p) -} diff --git a/examples/napi/src/lib.rs b/examples/napi/src/lib.rs index f1789ff7..e4d66c0c 100644 --- a/examples/napi/src/lib.rs +++ b/examples/napi/src/lib.rs @@ -11,4 +11,5 @@ mod error; mod nullable; mod number; mod object; +mod serde; mod string; diff --git a/examples/napi/src/serde.rs b/examples/napi/src/serde.rs new file mode 100644 index 00000000..4ff31c1f --- /dev/null +++ b/examples/napi/src/serde.rs @@ -0,0 +1,25 @@ +use napi::bindgen_prelude::*; +use serde_json::{Map, Value}; +use std::fs; + +#[napi(object)] +#[derive(Serialize, Deserialize, Debug)] +struct PackageJson { + pub name: String, + pub version: String, + pub dependencies: Option>, + #[serde(rename = "devDependencies")] + pub dev_dependencies: Option>, +} + +#[napi] +fn read_package_json() -> Result { + let raw = fs::read_to_string("package.json")?; + let p: PackageJson = serde_json::from_str(&raw)?; + Ok(p) +} + +#[napi] +fn get_package_json_name(package_json: PackageJson) -> String { + package_json.name +}