serde support
This commit is contained in:
parent
8c281fd174
commit
5b39035541
20 changed files with 415 additions and 185 deletions
|
@ -219,7 +219,7 @@ async function findUp(dir = process.cwd()): Promise<string | null> {
|
|||
}
|
||||
|
||||
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')
|
||||
}
|
||||
|
|
|
@ -51,7 +51,14 @@ pub struct NapiStruct {
|
|||
pub vis: syn::Visibility,
|
||||
pub fields: Vec<NapiStructField>,
|
||||
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)]
|
||||
|
|
|
@ -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<sys::napi_value> {
|
||||
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<sys::napi_value> {
|
||||
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<sys::napi_value> {
|
||||
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<Self> {
|
||||
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<TokenStream> {
|
||||
|
@ -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) });
|
||||
}
|
||||
|
||||
|
|
|
@ -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<HashMap<&'static str, Regex>> = Lazy::new(|| {
|
||||
static KNOWN_TYPES: Lazy<HashMap<&'static str, &'static str>> = 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<string, any>"),
|
||||
("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>) -> String {
|
||||
let matches = template.match_indices("{}").collect::<Vec<_>>();
|
||||
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::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
if tuple.elems.is_empty() {
|
||||
"undefined".to_owned()
|
||||
} else {
|
||||
format!(
|
||||
"[{}]",
|
||||
tuple
|
||||
.elems
|
||||
.iter()
|
||||
.map(|elem| ty_to_ts_type(elem, false))
|
||||
.collect::<Vec<_>>()
|
||||
.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::<Vec<_>>()
|
||||
} 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(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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::<Vec<_>>()
|
||||
.join("\\n");
|
||||
|
||||
if self.gen_default_ctor {
|
||||
if self.kind == NapiStructKind::Constructor {
|
||||
format!("{}\\nconstructor({})", def, ctor_args.join(", "))
|
||||
} else {
|
||||
def
|
||||
|
|
|
@ -49,6 +49,7 @@ macro_rules! attrgen {
|
|||
(readonly, Readonly(Span)),
|
||||
(skip, Skip(Span)),
|
||||
(strict, Strict(Span)),
|
||||
(object, Object(Span)),
|
||||
|
||||
// impl later
|
||||
// (inspectable, Inspectable(Span)),
|
||||
|
|
|
@ -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,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -56,7 +56,6 @@ impl<const N: usize> CallbackInfo<N> {
|
|||
|
||||
pub fn construct<T>(&self, js_name: &str, obj: T) -> Result<sys::napi_value> {
|
||||
let obj = Box::new(obj);
|
||||
let mut result = std::ptr::null_mut();
|
||||
let this = self.this();
|
||||
|
||||
unsafe {
|
||||
|
@ -67,7 +66,7 @@ impl<const N: usize> CallbackInfo<N> {
|
|||
Box::into_raw(obj) as *mut std::ffi::c_void,
|
||||
Some(raw_finalize_unchecked::<T>),
|
||||
ptr::null_mut(),
|
||||
&mut result
|
||||
&mut std::ptr::null_mut()
|
||||
),
|
||||
"Failed to initialize class `{}`",
|
||||
js_name,
|
||||
|
|
|
@ -8,6 +8,8 @@ mod map;
|
|||
mod nil;
|
||||
mod number;
|
||||
mod object;
|
||||
#[cfg(feature = "serde-json")]
|
||||
mod serde;
|
||||
mod string;
|
||||
|
||||
pub use array::*;
|
||||
|
|
|
@ -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 {
|
||||
|
|
95
crates/napi/src/bindgen_runtime/js_values/serde.rs
Normal file
95
crates/napi/src/bindgen_runtime/js_values/serde.rs
Normal file
|
@ -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<sys::napi_value> {
|
||||
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::<Value>::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<Self> {
|
||||
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::<Value>::from_napi_value(env, napi_val)?)
|
||||
} else {
|
||||
Value::Object(Map::<String, Value>::from_napi_value(env, napi_val)?)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "napi6")]
|
||||
ValueType::Bigint => todo!(),
|
||||
_ => Value::Null,
|
||||
};
|
||||
|
||||
Ok(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToNapiValue for Map<String, Value> {
|
||||
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
|
||||
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<String, Value> {
|
||||
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -13,7 +13,6 @@ Generated by [AVA](https://avajs.dev).
|
|||
export function sumNums(nums: Array<number>): 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<string>␊
|
||||
export function createObj(): object␊
|
||||
interface PackageJson {␊
|
||||
name: string␊
|
||||
version: string␊
|
||||
dependencies: Record<string, any> | null␊
|
||||
devDependencies: Record<string, any> | 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<string, string> | null␊
|
||||
devDependencies: Record<string, string> | null␊
|
||||
constructor(name: string, version: string, dependencies: Record<string, string> | null, devDependencies: Record<string, string> | null)␊
|
||||
}␊
|
||||
`
|
||||
|
|
Binary file not shown.
|
@ -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')
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Binary file not shown.
20
examples/napi/index.d.ts
vendored
20
examples/napi/index.d.ts
vendored
|
@ -3,7 +3,6 @@ export function getNums(): Array<number>
|
|||
export function sumNums(nums: Array<number>): 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<string>
|
||||
export function createObj(): object
|
||||
interface PackageJson {
|
||||
name: string
|
||||
version: string
|
||||
dependencies: Record<string, any> | null
|
||||
devDependencies: Record<string, any> | 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<string, string> | null
|
||||
devDependencies: Record<string, string> | null
|
||||
constructor(name: string, version: string, dependencies: Record<string, string> | null, devDependencies: Record<string, string> | null)
|
||||
}
|
||||
|
|
|
@ -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<HashMap<String, String>>,
|
||||
#[serde(rename = "devDependencies")]
|
||||
pub dev_dependencies: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
fn read_package_json() -> Result<PackageJson> {
|
||||
let raw = fs::read_to_string("package.json")?;
|
||||
let p: PackageJson = serde_json::from_str(&raw)?;
|
||||
Ok(p)
|
||||
}
|
||||
|
|
|
@ -11,4 +11,5 @@ mod error;
|
|||
mod nullable;
|
||||
mod number;
|
||||
mod object;
|
||||
mod serde;
|
||||
mod string;
|
||||
|
|
25
examples/napi/src/serde.rs
Normal file
25
examples/napi/src/serde.rs
Normal file
|
@ -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<Map<String, Value>>,
|
||||
#[serde(rename = "devDependencies")]
|
||||
pub dev_dependencies: Option<Map<String, Value>>,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
fn read_package_json() -> Result<PackageJson> {
|
||||
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
|
||||
}
|
Loading…
Reference in a new issue