feat(napi-derive): allow partial implement From/To Napivalue for Object (#1448)

This commit is contained in:
LongYinan 2023-01-24 14:51:16 +08:00 committed by GitHub
parent e79eb34118
commit c8352a1fb0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 99 additions and 27 deletions

View file

@ -80,6 +80,8 @@ pub struct NapiStruct {
pub fields: Vec<NapiStructField>, pub fields: Vec<NapiStructField>,
pub is_tuple: bool, pub is_tuple: bool,
pub kind: NapiStructKind, pub kind: NapiStructKind,
pub object_from_js: bool,
pub object_to_js: bool,
pub js_mod: Option<String>, pub js_mod: Option<String>,
pub comments: Vec<String>, pub comments: Vec<String>,
pub implement_iterator: bool, pub implement_iterator: bool,

View file

@ -558,6 +558,46 @@ impl NapiStruct {
} }
}; };
let to_napi_value = if self.object_to_js {
quote! {
impl napi::bindgen_prelude::ToNapiValue for #name {
unsafe fn to_napi_value(env: napi::bindgen_prelude::sys::napi_env, val: #name) -> napi::bindgen_prelude::Result<napi::bindgen_prelude::sys::napi_value> {
let env_wrapper = napi::bindgen_prelude::Env::from(env);
let mut obj = env_wrapper.create_object()?;
let #destructed_fields = val;
#(#obj_field_setters)*
napi::bindgen_prelude::Object::to_napi_value(env, obj)
}
}
}
} else {
quote! {}
};
let from_napi_value = if self.object_from_js {
quote! {
impl napi::bindgen_prelude::FromNapiValue for #name {
unsafe fn from_napi_value(
env: napi::bindgen_prelude::sys::napi_env,
napi_val: napi::bindgen_prelude::sys::napi_value
) -> napi::bindgen_prelude::Result<Self> {
let env_wrapper = napi::bindgen_prelude::Env::from(env);
let mut obj = napi::bindgen_prelude::Object::from_napi_value(env, napi_val)?;
#(#obj_field_getters)*
let val = #destructed_fields;
Ok(val)
}
}
}
} else {
quote! {}
};
quote! { quote! {
impl napi::bindgen_prelude::TypeName for #name { impl napi::bindgen_prelude::TypeName for #name {
fn type_name() -> &'static str { fn type_name() -> &'static str {
@ -569,33 +609,9 @@ impl NapiStruct {
} }
} }
impl napi::bindgen_prelude::ToNapiValue for #name { #to_napi_value
unsafe fn to_napi_value(env: napi::bindgen_prelude::sys::napi_env, val: #name) -> napi::bindgen_prelude::Result<napi::bindgen_prelude::sys::napi_value> {
let env_wrapper = napi::bindgen_prelude::Env::from(env);
let mut obj = env_wrapper.create_object()?;
let #destructed_fields = val; #from_napi_value
#(#obj_field_setters)*
napi::bindgen_prelude::Object::to_napi_value(env, obj)
}
}
impl napi::bindgen_prelude::FromNapiValue for #name {
unsafe fn from_napi_value(
env: napi::bindgen_prelude::sys::napi_env,
napi_val: napi::bindgen_prelude::sys::napi_value
) -> napi::bindgen_prelude::Result<Self> {
let env_wrapper = napi::bindgen_prelude::Env::from(env);
let mut obj = napi::bindgen_prelude::Object::from_napi_value(env, napi_val)?;
#(#obj_field_getters)*
let val = #destructed_fields;
Ok(val)
}
}
impl napi::bindgen_prelude::ValidateNapiValue for #name {} impl napi::bindgen_prelude::ValidateNapiValue for #name {}
} }

View file

@ -56,6 +56,8 @@ macro_rules! attrgen {
(strict, Strict(Span)), (strict, Strict(Span)),
(return_if_invalid, ReturnIfInvalid(Span)), (return_if_invalid, ReturnIfInvalid(Span)),
(object, Object(Span)), (object, Object(Span)),
(object_from_js, ObjectFromJs(Span, Option<bool>)),
(object_to_js, ObjectToJs(Span, Option<bool>)),
(custom_finalize, CustomFinalize(Span)), (custom_finalize, CustomFinalize(Span)),
(namespace, Namespace(Span, String, Span)), (namespace, Namespace(Span, String, Span)),
(iterator, Iterator(Span)), (iterator, Iterator(Span)),

View file

@ -995,6 +995,8 @@ impl ConvertToAST for syn::ItemStruct {
fields, fields,
is_tuple, is_tuple,
kind: struct_kind, kind: struct_kind,
object_from_js: opts.object_from_js(),
object_to_js: opts.object_to_js(),
js_mod: namespace, js_mod: namespace,
comments: extract_doc_comments(&self.attrs), comments: extract_doc_comments(&self.attrs),
implement_iterator, implement_iterator,

View file

@ -172,6 +172,11 @@ Generated by [AVA](https://avajs.dev).
}␊ }␊
export function createObjWithProperty(): { value: ArrayBuffer, get getter(): number }␊ export function createObjWithProperty(): { value: ArrayBuffer, get getter(): number }␊
export function getterFromObj(): number␊ export function getterFromObj(): number␊
export interface ObjectOnlyFromJs {␊
count: number␊
callback: (err: Error | null, value: number) => any␊
}␊
export function receiveObjectOnlyFromJs(obj: { count: number, callback: (err: Error | null, count: number) => void }): void␊
export function asyncPlus100(p: Promise<number>): Promise<number> export function asyncPlus100(p: Promise<number>): Promise<number>
/** This is an interface for package.json */␊ /** This is an interface for package.json */␊
export interface PackageJson {␊ export interface PackageJson {␊

View file

@ -89,6 +89,7 @@ import {
returnJsFunction, returnJsFunction,
testSerdeRoundtrip, testSerdeRoundtrip,
createObjWithProperty, createObjWithProperty,
receiveObjectOnlyFromJs,
dateToNumber, dateToNumber,
chronoDateToMillis, chronoDateToMillis,
derefUint8Array, derefUint8Array,
@ -816,6 +817,22 @@ Napi4Test('accept ThreadsafeFunction Fatal', async (t) => {
}) })
}) })
Napi4Test('object only from js', (t) => {
return new Promise((resolve, reject) => {
receiveObjectOnlyFromJs({
count: 100,
callback: (err: Error | null, count: number) => {
if (err) {
reject(err)
} else {
t.is(count, 100)
resolve()
}
},
})
})
})
const Napi5Test = Number(process.versions.napi) >= 5 ? test : test.skip const Napi5Test = Number(process.versions.napi) >= 5 ? test : test.skip
Napi5Test('Date test', (t) => { Napi5Test('Date test', (t) => {

View file

@ -162,6 +162,11 @@ export interface TsTypeChanged {
} }
export function createObjWithProperty(): { value: ArrayBuffer, get getter(): number } export function createObjWithProperty(): { value: ArrayBuffer, get getter(): number }
export function getterFromObj(): number export function getterFromObj(): number
export interface ObjectOnlyFromJs {
count: number
callback: (err: Error | null, value: number) => any
}
export function receiveObjectOnlyFromJs(obj: { count: number, callback: (err: Error | null, count: number) => void }): void
export function asyncPlus100(p: Promise<number>): Promise<number> export function asyncPlus100(p: Promise<number>): Promise<number>
/** This is an interface for package.json */ /** This is an interface for package.json */
export interface PackageJson { export interface PackageJson {

View file

@ -1,4 +1,7 @@
use napi::{bindgen_prelude::*, JsGlobal, JsNull, JsObject, JsUndefined, Property}; use napi::{
bindgen_prelude::*, threadsafe_function::ThreadsafeFunction, JsGlobal, JsNull, JsObject,
JsUndefined, Property,
};
#[napi] #[napi]
fn list_obj_keys(obj: Object) -> Vec<String> { fn list_obj_keys(obj: Object) -> Vec<String> {
@ -101,3 +104,23 @@ pub fn create_obj_with_property(env: Env) -> Result<JsObject> {
fn getter_from_obj() -> u32 { fn getter_from_obj() -> u32 {
42 42
} }
#[napi(object, object_to_js = false)]
struct ObjectOnlyFromJs {
pub count: u32,
pub callback: ThreadsafeFunction<u32>,
}
#[napi]
fn receive_object_only_from_js(
#[napi(ts_arg_type = "{ count: number, callback: (err: Error | null, count: number) => void }")]
obj: ObjectOnlyFromJs,
) {
let ObjectOnlyFromJs { callback, count } = obj;
std::thread::spawn(move || {
callback.call(
Ok(count),
napi::threadsafe_function::ThreadsafeFunctionCallMode::NonBlocking,
);
});
}