feat(napi): implement either type

This commit is contained in:
LongYinan 2020-07-18 02:00:48 +08:00
parent 23d8b03a24
commit 3508956d16
No known key found for this signature in database
GPG key ID: C3666B7FC82ADAD7
16 changed files with 156 additions and 59 deletions

View file

@ -111,7 +111,7 @@ pub fn js_function(attr: TokenStream, input: TokenStream) -> TokenStream {
} }
let mut env = Env::from_raw(raw_env); let mut env = Env::from_raw(raw_env);
let call_ctx = CallContext::new(&mut env, raw_this, &raw_args, #arg_len_span); let call_ctx = CallContext::new(&mut env, raw_this, &raw_args, #arg_len_span, argc as usize);
let result = call_ctx.and_then(|ctx| #new_fn_name(ctx)); let result = call_ctx.and_then(|ctx| #new_fn_name(ctx));
has_error = has_error && result.is_err(); has_error = has_error && result.is_err();

View file

@ -1,10 +1,11 @@
use crate::{sys, Env, Error, JsUnknown, NapiValue, Result, Status}; use crate::{sys, Either, Env, Error, JsUndefined, JsUnknown, NapiValue, Result, Status};
pub struct CallContext<'env, T: NapiValue = JsUnknown> { pub struct CallContext<'env, T: NapiValue = JsUnknown> {
pub env: &'env Env, pub env: &'env Env,
pub this: T, pub this: T,
args: &'env [sys::napi_value], args: &'env [sys::napi_value],
arg_len: usize, arg_len: usize,
actual_arg_length: usize,
} }
impl<'env, T: NapiValue> CallContext<'env, T> { impl<'env, T: NapiValue> CallContext<'env, T> {
@ -14,12 +15,14 @@ impl<'env, T: NapiValue> CallContext<'env, T> {
this: sys::napi_value, this: sys::napi_value,
args: &'env [sys::napi_value], args: &'env [sys::napi_value],
arg_len: usize, arg_len: usize,
actual_arg_length: usize,
) -> Result<Self> { ) -> Result<Self> {
Ok(Self { Ok(Self {
env, env,
this: T::from_raw(env.0, this)?, this: T::from_raw(env.0, this)?,
args, args,
arg_len, arg_len,
actual_arg_length,
}) })
} }
@ -34,4 +37,20 @@ impl<'env, T: NapiValue> CallContext<'env, T> {
ArgType::from_raw(self.env.0, self.args[index]) ArgType::from_raw(self.env.0, self.args[index])
} }
} }
#[inline]
pub fn try_get<ArgType: NapiValue>(&self, index: usize) -> Result<Either<ArgType, JsUndefined>> {
if index + 1 > self.arg_len {
Err(Error {
status: Status::GenericFailure,
reason: "Arguments index out of range".to_owned(),
})
} else {
if index < self.actual_arg_length {
ArgType::from_raw(self.env.0, self.args[index]).map(Either::A)
} else {
self.env.get_undefined().map(Either::B)
}
}
}
} }

View file

@ -312,7 +312,7 @@ impl Env {
check_status(status)?; check_status(status)?;
}; };
Ok(T::from_raw_unchecked(self.0, raw_value)) T::from_raw(self.0, raw_value)
} }
#[inline] #[inline]
@ -455,12 +455,7 @@ impl Env {
let reason_string = self.create_string(reason.as_str())?; let reason_string = self.create_string(reason.as_str())?;
let mut result = ptr::null_mut(); let mut result = ptr::null_mut();
let status = unsafe { let status = unsafe {
sys::napi_create_error( sys::napi_create_error(self.0, ptr::null_mut(), reason_string.0.value, &mut result)
self.0,
ptr::null_mut(),
reason_string.into_raw(),
&mut result,
)
}; };
check_status(status)?; check_status(status)?;
Ok(JsObject::from_raw_unchecked(self.0, result)) Ok(JsObject::from_raw_unchecked(self.0, result))

View file

@ -11,6 +11,20 @@ pub struct JsArrayBuffer {
pub len: u64, pub len: u64,
} }
impl JsArrayBuffer {
pub(crate) fn from_raw_unchecked(env: sys::napi_env, value: sys::napi_value) -> Self {
Self {
value: JsObject(Value {
env,
value,
value_type: ValueType::Object,
}),
data: ptr::null(),
len: 0,
}
}
}
impl NapiValue for JsArrayBuffer { impl NapiValue for JsArrayBuffer {
fn raw_value(&self) -> sys::napi_value { fn raw_value(&self) -> sys::napi_value {
self.value.0.value self.value.0.value
@ -31,16 +45,4 @@ impl NapiValue for JsArrayBuffer {
len, len,
}) })
} }
fn from_raw_unchecked(env: sys::napi_env, value: sys::napi_value) -> Self {
Self {
value: JsObject(Value {
env,
value,
value_type: ValueType::Object,
}),
data: ptr::null(),
len: 0,
}
}
} }

View file

@ -14,6 +14,18 @@ pub struct JsBuffer {
} }
impl JsBuffer { impl JsBuffer {
pub(crate) fn from_raw_unchecked(env: sys::napi_env, value: sys::napi_value) -> Self {
Self {
value: JsObject(Value {
env,
value,
value_type: ValueType::Object,
}),
data: ptr::null(),
len: 0,
}
}
pub fn into_unknown(self) -> Result<JsUnknown> { pub fn into_unknown(self) -> Result<JsUnknown> {
self.value.into_unknown() self.value.into_unknown()
} }
@ -39,18 +51,6 @@ impl NapiValue for JsBuffer {
len, len,
}) })
} }
fn from_raw_unchecked(env: sys::napi_env, value: sys::napi_value) -> Self {
Self {
value: JsObject(Value {
env,
value,
value_type: ValueType::Object,
}),
data: ptr::null(),
len: 0,
}
}
} }
impl AsRef<[u8]> for JsBuffer { impl AsRef<[u8]> for JsBuffer {

View file

@ -41,7 +41,7 @@ impl Property {
} }
pub(crate) fn into_raw(mut self, env: &Env) -> Result<sys::napi_property_descriptor> { pub(crate) fn into_raw(mut self, env: &Env) -> Result<sys::napi_property_descriptor> {
self.raw_descriptor.name = env.create_string(&self.name)?.into_raw(); self.raw_descriptor.name = env.create_string(&self.name)?.raw_value();
Ok(self.raw_descriptor) Ok(self.raw_descriptor)
} }
} }

View file

@ -0,0 +1,31 @@
use crate::{sys, JsUndefined, NapiValue, Result};
#[derive(Debug, Clone, Copy)]
pub enum Either<A: NapiValue, B: NapiValue> {
A(A),
B(B),
}
impl<T: NapiValue> Into<Option<T>> for Either<T, JsUndefined> {
fn into(self) -> Option<T> {
match self {
Either::A(v) => Some(v),
Either::B(_) => None,
}
}
}
impl<A: NapiValue, B: NapiValue> NapiValue for Either<A, B> {
fn from_raw(env: sys::napi_env, value: sys::napi_value) -> Result<Either<A, B>> {
A::from_raw(env, value)
.map(Self::A)
.or_else(|_| B::from_raw(env, value).map(Self::B))
}
fn raw_value(&self) -> sys::napi_value {
match self {
Either::A(v) => v.raw_value(),
Either::B(v) => v.raw_value(),
}
}
}

View file

@ -26,12 +26,12 @@ impl JsFunction {
/// [napi_call_function](https://nodejs.org/api/n-api.html#n_api_napi_call_function) /// [napi_call_function](https://nodejs.org/api/n-api.html#n_api_napi_call_function)
pub fn call(&self, this: Option<&JsObject>, args: &[JsUnknown]) -> Result<JsUnknown> { pub fn call(&self, this: Option<&JsObject>, args: &[JsUnknown]) -> Result<JsUnknown> {
let raw_this = this let raw_this = this
.map(|v| v.into_raw()) .map(|v| v.raw_value())
.or_else(|| { .or_else(|| {
Env::from_raw(self.0.env) Env::from_raw(self.0.env)
.get_undefined() .get_undefined()
.ok() .ok()
.map(|u| u.into_raw()) .map(|u| u.raw_value())
}) })
.ok_or(Error::new( .ok_or(Error::new(
Status::Unknown, Status::Unknown,

View file

@ -8,11 +8,13 @@ mod arraybuffer;
mod boolean; mod boolean;
mod buffer; mod buffer;
mod class_property; mod class_property;
mod either;
mod function; mod function;
mod number; mod number;
mod object; mod object;
mod string; mod string;
mod tagged_object; mod tagged_object;
mod undefined;
mod value; mod value;
mod value_ref; mod value_ref;
mod value_type; mod value_type;
@ -21,11 +23,13 @@ pub use arraybuffer::JsArrayBuffer;
pub use boolean::JsBoolean; pub use boolean::JsBoolean;
pub use buffer::JsBuffer; pub use buffer::JsBuffer;
pub use class_property::Property; pub use class_property::Property;
pub use either::Either;
pub use function::JsFunction; pub use function::JsFunction;
pub use number::JsNumber; pub use number::JsNumber;
pub use object::JsObject; pub use object::JsObject;
pub use string::JsString; pub use string::JsString;
pub(crate) use tagged_object::TaggedObject; pub(crate) use tagged_object::TaggedObject;
pub use undefined::JsUndefined;
pub(crate) use value::Value; pub(crate) use value::Value;
pub(crate) use value_ref::Ref; pub(crate) use value_ref::Ref;
pub use value_type::ValueType; pub use value_type::ValueType;
@ -34,9 +38,6 @@ pub use value_type::ValueType;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct JsUnknown(pub(crate) Value); pub struct JsUnknown(pub(crate) Value);
#[derive(Clone, Copy, Debug)]
pub struct JsUndefined(pub(crate) Value);
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct JsNull(pub(crate) Value); pub struct JsNull(pub(crate) Value);
@ -78,17 +79,20 @@ macro_rules! impl_napi_value_trait {
} }
} }
fn from_raw_unchecked(env: sys::napi_env, value: sys::napi_value) -> Self { fn raw_value(&self) -> sys::napi_value {
self.0.value
}
}
impl $js_value {
#[inline]
pub fn from_raw_unchecked(env: sys::napi_env, value: sys::napi_value) -> Self {
Self(Value { Self(Value {
env, env,
value, value,
value_type: $value_type, value_type: $value_type,
}) })
} }
fn raw_value(&self) -> sys::napi_value {
self.0.value
}
} }
}; };
} }
@ -96,12 +100,12 @@ macro_rules! impl_napi_value_trait {
macro_rules! impl_js_value_methods { macro_rules! impl_js_value_methods {
($js_value:ident) => { ($js_value:ident) => {
impl $js_value { impl $js_value {
pub fn into_raw(self) -> sys::napi_value { #[inline]
self.0.value
}
pub fn into_unknown(self) -> Result<JsUnknown> { pub fn into_unknown(self) -> Result<JsUnknown> {
JsUnknown::from_raw(self.0.env, self.0.value) JsUnknown::from_raw(self.0.env, self.0.value)
} }
#[inline]
pub fn coerce_to_number(self) -> Result<JsNumber> { pub fn coerce_to_number(self) -> Result<JsNumber> {
let mut new_raw_value = ptr::null_mut(); let mut new_raw_value = ptr::null_mut();
let status = let status =
@ -113,6 +117,7 @@ macro_rules! impl_js_value_methods {
value_type: ValueType::Number, value_type: ValueType::Number,
})) }))
} }
#[inline]
pub fn coerce_to_string(self) -> Result<JsString> { pub fn coerce_to_string(self) -> Result<JsString> {
let mut new_raw_value = ptr::null_mut(); let mut new_raw_value = ptr::null_mut();
let status = let status =
@ -152,8 +157,6 @@ macro_rules! impl_js_value_methods {
pub trait NapiValue: Sized { pub trait NapiValue: Sized {
fn from_raw(env: sys::napi_env, value: sys::napi_value) -> Result<Self>; fn from_raw(env: sys::napi_env, value: sys::napi_value) -> Result<Self>;
fn from_raw_unchecked(env: sys::napi_env, value: sys::napi_value) -> Self;
fn raw_value(&self) -> sys::napi_value; fn raw_value(&self) -> sys::napi_value;
} }
@ -186,15 +189,11 @@ impl_napi_value_trait!(JsSymbol, Symbol);
impl NapiValue for JsUnknown { impl NapiValue for JsUnknown {
fn from_raw(env: sys::napi_env, value: sys::napi_value) -> Result<Self> { fn from_raw(env: sys::napi_env, value: sys::napi_value) -> Result<Self> {
Ok(Self::from_raw_unchecked(env, value)) Ok(JsUnknown(Value {
}
fn from_raw_unchecked(env: sys::napi_env, value: sys::napi_value) -> JsUnknown {
JsUnknown(Value {
env, env,
value, value,
value_type: Unknown, value_type: Unknown,
}) }))
} }
fn raw_value(&self) -> sys::napi_value { fn raw_value(&self) -> sys::napi_value {

View file

@ -7,7 +7,7 @@ use std::str;
use super::Value; use super::Value;
use crate::error::check_status; use crate::error::check_status;
use crate::{sys, Error, JsNumber, NapiValue, Result, Status}; use crate::{sys, Error, JsNumber, Result, Status};
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct JsString(pub(crate) Value); pub struct JsString(pub(crate) Value);

View file

@ -0,0 +1,4 @@
use super::Value;
#[derive(Clone, Copy, Debug)]
pub struct JsUndefined(pub(crate) Value);

View file

@ -159,7 +159,7 @@ macro_rules! register_module {
let hook_result = Ok(()); let hook_result = Ok(());
match hook_result.and_then(move |_| result) { match hook_result.and_then(move |_| result) {
Ok(_) => exports.into_raw(), Ok(_) => exports.raw_value(),
Err(e) => { Err(e) => {
unsafe { unsafe {
sys::napi_throw_error( sys::napi_throw_error(

View file

@ -207,9 +207,9 @@ unsafe extern "C" fn call_js_cb<T: ToJs>(
let values = ret.unwrap(); let values = ret.unwrap();
let js_null = env.get_null().unwrap(); let js_null = env.get_null().unwrap();
let mut raw_values: Vec<sys::napi_value> = vec![]; let mut raw_values: Vec<sys::napi_value> = vec![];
raw_values.push(js_null.into_raw()); raw_values.push(js_null.0.value);
for item in values.iter() { for item in values.iter() {
raw_values.push(item.into_raw()) raw_values.push(item.0.value)
} }
status = sys::napi_call_function( status = sys::napi_call_function(

View file

@ -0,0 +1,14 @@
const test = require('ava')
const bindings = require('../index.node')
test('either should work', (t) => {
const fixture = 'napi'
t.is(bindings.eitherNumberString(1), 101)
t.is(bindings.eitherNumberString(fixture), `Either::B(${fixture})`)
})
test('dynamic argument length should work', (t) => {
t.is(bindings.dynamicArgumentLength(1), 101)
t.is(bindings.dynamicArgumentLength(), 42)
})

29
test_module/src/either.rs Normal file
View file

@ -0,0 +1,29 @@
use std::convert::TryInto;
use napi::{CallContext, Either, JsNumber, JsString, Result};
#[js_function(1)]
pub fn either_number_string(ctx: CallContext) -> Result<Either<JsNumber, JsString>> {
let arg = ctx.get::<Either<JsNumber, JsString>>(0)?;
match arg {
Either::A(n) => {
let n: u32 = n.try_into()?;
ctx.env.create_uint32(n + 100).map(Either::A)
}
Either::B(s) => {
let content = format!("Either::B({})", s.as_str()?);
ctx.env.create_string_from_std(content).map(Either::B)
}
}
}
#[js_function(1)]
pub fn dynamic_argument_length(ctx: CallContext) -> Result<JsNumber> {
let value: Option<JsNumber> = ctx.try_get::<JsNumber>(0)?.into();
if let Some(n) = value {
let n: u32 = n.try_into()?;
ctx.env.create_uint32(n + 100)
} else {
ctx.env.create_uint32(42)
}
}

View file

@ -15,6 +15,7 @@ mod napi5;
mod tokio_rt; mod tokio_rt;
mod buffer; mod buffer;
mod either;
mod external; mod external;
mod function; mod function;
mod napi_version; mod napi_version;
@ -22,6 +23,7 @@ mod symbol;
mod task; mod task;
use buffer::{buffer_to_string, get_buffer_length}; use buffer::{buffer_to_string, get_buffer_length};
use either::{dynamic_argument_length, either_number_string};
use external::{create_external, get_external_count}; use external::{create_external, get_external_count};
use function::{call_function, call_function_with_this}; use function::{call_function, call_function_with_this};
#[cfg(napi4)] #[cfg(napi4)]
@ -52,6 +54,8 @@ fn init(module: &mut Module) -> Result<()> {
module.create_named_method("getNapiVersion", get_napi_version)?; module.create_named_method("getNapiVersion", get_napi_version)?;
module.create_named_method("testCallFunction", call_function)?; module.create_named_method("testCallFunction", call_function)?;
module.create_named_method("testCallFunctionWithThis", call_function_with_this)?; module.create_named_method("testCallFunctionWithThis", call_function_with_this)?;
module.create_named_method("eitherNumberString", either_number_string)?;
module.create_named_method("dynamicArgumentLength", dynamic_argument_length)?;
#[cfg(napi4)] #[cfg(napi4)]
module.create_named_method("testExecuteTokioReadfile", test_execute_tokio_readfile)?; module.create_named_method("testExecuteTokioReadfile", test_execute_tokio_readfile)?;
#[cfg(napi4)] #[cfg(napi4)]