fix(napi-derive-backend): do not unwrap Option value in object getter if the type of field is Option<T>

This commit is contained in:
LongYinan 2021-12-02 15:47:59 +08:00
parent d58e488fa2
commit 6d4b4af36f
No known key found for this signature in database
GPG key ID: C3666B7FC82ADAD7
7 changed files with 55 additions and 4 deletions

View file

@ -239,20 +239,40 @@ impl NapiStruct {
for field in self.fields.iter() { for field in self.fields.iter() {
let field_js_name = &field.js_name; let field_js_name = &field.js_name;
let ty = &field.ty; let ty = &field.ty;
let is_optional_field = if let syn::Type::Path(syn::TypePath {
path: syn::Path { segments, .. },
..
}) = &ty
{
if let Some(last_path) = segments.last() {
last_path.ident.to_string() == "Option"
} else {
false
}
} else {
false
};
match &field.name { match &field.name {
syn::Member::Named(ident) => { syn::Member::Named(ident) => {
field_destructions.push(quote! { #ident }); field_destructions.push(quote! { #ident });
obj_field_setters.push(quote! { obj.set(#field_js_name, #ident)?; }); obj_field_setters.push(quote! { obj.set(#field_js_name, #ident)?; });
if is_optional_field {
obj_field_getters.push(quote! { let #ident: #ty = obj.get(#field_js_name)?; });
} else {
obj_field_getters.push(quote! { let #ident: #ty = obj.get(#field_js_name)?.expect(&format!("Field {} should exist", #field_js_name)); }); 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) => { syn::Member::Unnamed(i) => {
field_destructions.push(quote! { arg#i }); field_destructions.push(quote! { arg#i });
obj_field_setters.push(quote! { obj.set(#field_js_name, arg#1)?; }); obj_field_setters.push(quote! { obj.set(#field_js_name, arg#1)?; });
if is_optional_field {
obj_field_getters.push(quote! { let arg#i: #ty = obj.get(#field_js_name)?; });
} else {
obj_field_getters.push(quote! { let arg#i: #ty = obj.get(#field_js_name)?.expect(&format!("Field {} should exist", #field_js_name)); }); 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 { let destructed_fields = if self.is_tuple {
quote! { quote! {

View file

@ -35,7 +35,7 @@ impl Object {
let ty = type_of!(self.0.env, ret)?; let ty = type_of!(self.0.env, ret)?;
Ok(if ty == ValueType::Undefined { Ok(if ty == ValueType::Undefined || ty == ValueType::Null {
None None
} else { } else {
Some(V::from_napi_value(self.0.env, ret)?) Some(V::from_napi_value(self.0.env, ret)?)

View file

@ -73,6 +73,11 @@ Generated by [AVA](https://avajs.dev).
export function getGlobal(): typeof global␊ export function getGlobal(): typeof global␊
export function getUndefined(): void␊ export function getUndefined(): void␊
export function getNull(): JsNull␊ export function getNull(): JsNull␊
export interface AllOptionalObject {␊
name?: string | undefined | null␊
age?: number | undefined | null␊
}␊
export function receiveAllOptionalObject(obj?: AllOptionalObject | undefined | null): 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

@ -59,6 +59,7 @@ import {
convertU32Array, convertU32Array,
createExternalTypedArray, createExternalTypedArray,
mutateTypedArray, mutateTypedArray,
receiveAllOptionalObject,
} from '../' } from '../'
test('export const', (t) => { test('export const', (t) => {
@ -202,6 +203,11 @@ test('function ts type override', (t) => {
t.deepEqual(tsRename({ foo: 1, bar: 2, baz: 2 }), ['foo', 'bar', 'baz']) t.deepEqual(tsRename({ foo: 1, bar: 2, baz: 2 }), ['foo', 'bar', 'baz'])
}) })
test('option object', (t) => {
t.notThrows(() => receiveAllOptionalObject())
t.notThrows(() => receiveAllOptionalObject({}))
})
test('serde-json', (t) => { test('serde-json', (t) => {
const packageJson = readPackageJson() const packageJson = readPackageJson()
t.is(packageJson.name, 'napi-rs') t.is(packageJson.name, 'napi-rs')

View file

@ -63,6 +63,11 @@ export function createObj(): object
export function getGlobal(): typeof global export function getGlobal(): typeof global
export function getUndefined(): void export function getUndefined(): void
export function getNull(): JsNull export function getNull(): JsNull
export interface AllOptionalObject {
name?: string | undefined | null
age?: number | undefined | null
}
export function receiveAllOptionalObject(obj?: AllOptionalObject | undefined | null): 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

@ -27,3 +27,18 @@ fn get_undefined(env: Env) -> Result<JsUndefined> {
fn get_null(env: Env) -> Result<JsNull> { fn get_null(env: Env) -> Result<JsNull> {
env.get_null() env.get_null()
} }
#[napi(object)]
struct AllOptionalObject {
pub name: Option<String>,
pub age: Option<u32>,
}
#[napi]
fn receive_all_optional_object(obj: Option<AllOptionalObject>) -> Result<()> {
if !obj.is_none() {
assert!(obj.as_ref().unwrap().name.is_none());
assert!(obj.as_ref().unwrap().age.is_none());
}
Ok(())
}