serde support

This commit is contained in:
forehalo 2021-09-28 00:01:19 +08:00 committed by LongYinan
parent 8c281fd174
commit 5b39035541
20 changed files with 415 additions and 185 deletions

View file

@ -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')
}

View file

@ -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)]

View file

@ -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) });
}

View file

@ -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(),
}
}

View file

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

View file

@ -49,6 +49,7 @@ macro_rules! attrgen {
(readonly, Readonly(Span)),
(skip, Skip(Span)),
(strict, Strict(Span)),
(object, Object(Span)),
// impl later
// (inspectable, Inspectable(Span)),

View file

@ -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,
}),
})
}

View file

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

View file

@ -8,6 +8,8 @@ mod map;
mod nil;
mod number;
mod object;
#[cfg(feature = "serde-json")]
mod serde;
mod string;
pub use array::*;

View file

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

View 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)
}
}

View file

@ -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)␊
}␊
`

View file

@ -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')
})

View file

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

View file

@ -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)
}

View file

@ -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)
}

View file

@ -11,4 +11,5 @@ mod error;
mod nullable;
mod number;
mod object;
mod serde;
mod string;

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