Merge pull request #1201 from napi-rs/class-instance

feat(napi): support into_instance in class struct
This commit is contained in:
LongYinan 2022-06-04 01:26:16 +08:00 committed by GitHub
commit 04a865270c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 127 additions and 1 deletions

View file

@ -263,6 +263,21 @@ impl NapiStruct {
}
}
pub fn into_instance(self, env: napi::Env) -> napi::Result<napi::bindgen_prelude::ClassInstance<#name>> {
if let Some(ctor_ref) = napi::bindgen_prelude::get_class_constructor(#js_name_str) {
unsafe {
let wrapped_value = Box::leak(Box::new(self));
let instance_value = #name::new_instance(env.raw(), wrapped_value as *mut _ as *mut std::ffi::c_void, ctor_ref)?;
Ok(napi::bindgen_prelude::ClassInstance::<#name>::new(instance_value, wrapped_value))
}
} else {
Err(napi::bindgen_prelude::Error::new(
napi::bindgen_prelude::Status::InvalidArg, format!("Failed to get constructor of class `{}`", #js_name_raw))
)
}
}
unsafe fn new_instance(
env: napi::sys::napi_env,
wrapped_value: *mut std::ffi::c_void,

View file

@ -175,6 +175,7 @@ static KNOWN_TYPES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
("Buffer", "Buffer"),
("Vec", "Array<{}>"),
("Result", "Error | {}"),
("ClassInstance", "{}"),
("Either", "{} | {}"),
("Either3", "{} | {} | {}"),
("Either4", "{} | {} | {} | {}"),

View file

@ -8,6 +8,7 @@ mod arraybuffer;
mod bigint;
mod boolean;
mod buffer;
mod class;
#[cfg(all(feature = "chrono_date", feature = "napi5"))]
mod date;
mod either;
@ -34,6 +35,7 @@ pub use arraybuffer::*;
#[cfg(feature = "napi6")]
pub use bigint::*;
pub use buffer::*;
pub use class::*;
pub use either::*;
pub use external::*;
#[cfg(feature = "napi4")]

View file

@ -0,0 +1,65 @@
use std::any::type_name;
use std::ops::{Deref, DerefMut};
use std::ptr;
use crate::{bindgen_runtime::FromNapiValue, check_status, sys, NapiRaw};
pub struct ClassInstance<T: 'static> {
pub value: sys::napi_value,
inner: &'static mut T,
}
impl<T: 'static> ClassInstance<T> {
#[doc(hidden)]
pub fn new(value: sys::napi_value, inner: &'static mut T) -> Self {
Self { value, inner }
}
}
impl<T: 'static> NapiRaw for ClassInstance<T> {
unsafe fn raw(&self) -> sys::napi_value {
self.value
}
}
impl<T: 'static> FromNapiValue for ClassInstance<T> {
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> crate::Result<Self> {
let mut value = ptr::null_mut();
check_status!(
unsafe { sys::napi_unwrap(env, napi_val, &mut value) },
"Unwrap value [{}] from class failed",
type_name::<T>(),
)?;
let value = unsafe { Box::from_raw(value as *mut T) };
Ok(Self {
value: napi_val,
inner: Box::leak(value),
})
}
}
impl<T: 'static> Deref for ClassInstance<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.inner
}
}
impl<T: 'static> DerefMut for ClassInstance<T> {
fn deref_mut(&mut self) -> &mut T {
self.inner
}
}
impl<T: 'static> AsRef<T> for ClassInstance<T> {
fn as_ref(&self) -> &T {
self.inner
}
}
impl<T: 'static> AsMut<T> for ClassInstance<T> {
fn as_mut(&mut self) -> &mut T {
self.inner
}
}

View file

@ -41,6 +41,11 @@ Generated by [AVA](https://avajs.dev).
export function readFile(callback: (arg0: Error | undefined, arg1?: string | undefined | null) => void): void␊
export function returnJsFunction(): (...args: any[]) => any␊
export function callbackReturnPromise<T>(functionInput: () => T | Promise<T>, callback: (err: Error | null, result: T) => void): T | Promise<T>
export interface ObjectFieldClassInstance {␊
bird: Bird␊
}␊
export function createObjectWithClassField(): ObjectFieldClassInstance␊
export function receiveObjectWithClassField(object: ObjectFieldClassInstance): Bird␊
export function dateToNumber(input: Date): number␊
export function chronoDateToMillis(input: Date): number␊
export function chronoDateAdd1Minute(input: Date): Date␊

View file

@ -97,6 +97,8 @@ import {
eitherFromOption,
overrideIndividualArgOnFunction,
overrideIndividualArgOnFunctionWithCbArg,
createObjectWithClassField,
receiveObjectWithClassField,
} from '../'
test('export const', (t) => {
@ -206,6 +208,12 @@ test('class Factory return Result', (t) => {
t.is(c.method(), 'not empty')
})
test('class in object field', (t) => {
const obj = createObjectWithClassField()
t.is(obj.bird.name, 'Carolyn')
t.is(receiveObjectWithClassField(obj), obj.bird)
})
test('should be able to create object reference and shared reference', (t) => {
const repo = new JsRepo('.')
t.is(repo.remote().name(), 'origin')

View file

@ -31,6 +31,11 @@ export function optionOnly(callback: (arg0?: string | undefined | null) => void)
export function readFile(callback: (arg0: Error | undefined, arg1?: string | undefined | null) => void): void
export function returnJsFunction(): (...args: any[]) => any
export function callbackReturnPromise<T>(functionInput: () => T | Promise<T>, callback: (err: Error | null, result: T) => void): T | Promise<T>
export interface ObjectFieldClassInstance {
bird: Bird
}
export function createObjectWithClassField(): ObjectFieldClassInstance
export function receiveObjectWithClassField(object: ObjectFieldClassInstance): Bird
export function dateToNumber(input: Date): number
export function chronoDateToMillis(input: Date): number
export function chronoDateAdd1Minute(input: Date): Date

View file

@ -1,4 +1,7 @@
use napi::{bindgen_prelude::Buffer, Result};
use napi::{
bindgen_prelude::{Buffer, ClassInstance},
Env, Result,
};
use crate::r#enum::Kind;
@ -305,3 +308,25 @@ impl Optional {
}
}
}
#[napi(object)]
pub struct ObjectFieldClassInstance {
pub bird: ClassInstance<Bird>,
}
#[napi]
pub fn create_object_with_class_field(env: Env) -> Result<ObjectFieldClassInstance> {
Ok(ObjectFieldClassInstance {
bird: Bird {
name: "Carolyn".to_owned(),
}
.into_instance(env)?,
})
}
#[napi]
pub fn receive_object_with_class_field(
object: ObjectFieldClassInstance,
) -> Result<ClassInstance<Bird>> {
Ok(object.bird)
}