From 3508956d16d9aa27345a5a337c57451e02e20be8 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Sat, 18 Jul 2020 02:00:48 +0800 Subject: [PATCH] feat(napi): implement either type --- napi-derive/src/lib.rs | 2 +- napi/src/call_context.rs | 21 +++++++++++++++- napi/src/env.rs | 9 ++----- napi/src/js_values/arraybuffer.rs | 26 ++++++++++--------- napi/src/js_values/buffer.rs | 24 +++++++++--------- napi/src/js_values/class_property.rs | 2 +- napi/src/js_values/either.rs | 31 +++++++++++++++++++++++ napi/src/js_values/function.rs | 4 +-- napi/src/js_values/mod.rs | 37 ++++++++++++++-------------- napi/src/js_values/string.rs | 2 +- napi/src/js_values/undefined.rs | 4 +++ napi/src/lib.rs | 2 +- napi/src/threadsafe_function.rs | 4 +-- test_module/__test__/either.spec.js | 14 +++++++++++ test_module/src/either.rs | 29 ++++++++++++++++++++++ test_module/src/lib.rs | 4 +++ 16 files changed, 156 insertions(+), 59 deletions(-) create mode 100644 napi/src/js_values/either.rs create mode 100644 napi/src/js_values/undefined.rs create mode 100644 test_module/__test__/either.spec.js create mode 100644 test_module/src/either.rs diff --git a/napi-derive/src/lib.rs b/napi-derive/src/lib.rs index 77a0d350..69b7d475 100644 --- a/napi-derive/src/lib.rs +++ b/napi-derive/src/lib.rs @@ -111,7 +111,7 @@ pub fn js_function(attr: TokenStream, input: TokenStream) -> TokenStream { } 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)); has_error = has_error && result.is_err(); diff --git a/napi/src/call_context.rs b/napi/src/call_context.rs index 9e3f475c..f8cccff1 100644 --- a/napi/src/call_context.rs +++ b/napi/src/call_context.rs @@ -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 env: &'env Env, pub this: T, args: &'env [sys::napi_value], arg_len: usize, + actual_arg_length: usize, } impl<'env, T: NapiValue> CallContext<'env, T> { @@ -14,12 +15,14 @@ impl<'env, T: NapiValue> CallContext<'env, T> { this: sys::napi_value, args: &'env [sys::napi_value], arg_len: usize, + actual_arg_length: usize, ) -> Result { Ok(Self { env, this: T::from_raw(env.0, this)?, args, 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]) } } + + #[inline] + pub fn try_get(&self, index: usize) -> Result> { + 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) + } + } + } } diff --git a/napi/src/env.rs b/napi/src/env.rs index 2b4d2d98..236e4d83 100644 --- a/napi/src/env.rs +++ b/napi/src/env.rs @@ -312,7 +312,7 @@ impl Env { check_status(status)?; }; - Ok(T::from_raw_unchecked(self.0, raw_value)) + T::from_raw(self.0, raw_value) } #[inline] @@ -455,12 +455,7 @@ impl Env { let reason_string = self.create_string(reason.as_str())?; let mut result = ptr::null_mut(); let status = unsafe { - sys::napi_create_error( - self.0, - ptr::null_mut(), - reason_string.into_raw(), - &mut result, - ) + sys::napi_create_error(self.0, ptr::null_mut(), reason_string.0.value, &mut result) }; check_status(status)?; Ok(JsObject::from_raw_unchecked(self.0, result)) diff --git a/napi/src/js_values/arraybuffer.rs b/napi/src/js_values/arraybuffer.rs index e131fe67..c5b40617 100644 --- a/napi/src/js_values/arraybuffer.rs +++ b/napi/src/js_values/arraybuffer.rs @@ -11,6 +11,20 @@ pub struct JsArrayBuffer { 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 { fn raw_value(&self) -> sys::napi_value { self.value.0.value @@ -31,16 +45,4 @@ impl NapiValue for JsArrayBuffer { 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, - } - } } diff --git a/napi/src/js_values/buffer.rs b/napi/src/js_values/buffer.rs index dfe082c0..1c70f80f 100644 --- a/napi/src/js_values/buffer.rs +++ b/napi/src/js_values/buffer.rs @@ -14,6 +14,18 @@ pub struct 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 { self.value.into_unknown() } @@ -39,18 +51,6 @@ impl NapiValue for JsBuffer { 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 { diff --git a/napi/src/js_values/class_property.rs b/napi/src/js_values/class_property.rs index c44dbb9b..73cbfeda 100644 --- a/napi/src/js_values/class_property.rs +++ b/napi/src/js_values/class_property.rs @@ -41,7 +41,7 @@ impl Property { } pub(crate) fn into_raw(mut self, env: &Env) -> Result { - 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) } } diff --git a/napi/src/js_values/either.rs b/napi/src/js_values/either.rs new file mode 100644 index 00000000..55413d92 --- /dev/null +++ b/napi/src/js_values/either.rs @@ -0,0 +1,31 @@ +use crate::{sys, JsUndefined, NapiValue, Result}; + +#[derive(Debug, Clone, Copy)] +pub enum Either { + A(A), + B(B), +} + +impl Into> for Either { + fn into(self) -> Option { + match self { + Either::A(v) => Some(v), + Either::B(_) => None, + } + } +} + +impl NapiValue for Either { + fn from_raw(env: sys::napi_env, value: sys::napi_value) -> Result> { + 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(), + } + } +} diff --git a/napi/src/js_values/function.rs b/napi/src/js_values/function.rs index d2b6db82..7f3a6c8e 100644 --- a/napi/src/js_values/function.rs +++ b/napi/src/js_values/function.rs @@ -26,12 +26,12 @@ impl JsFunction { /// [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 { let raw_this = this - .map(|v| v.into_raw()) + .map(|v| v.raw_value()) .or_else(|| { Env::from_raw(self.0.env) .get_undefined() .ok() - .map(|u| u.into_raw()) + .map(|u| u.raw_value()) }) .ok_or(Error::new( Status::Unknown, diff --git a/napi/src/js_values/mod.rs b/napi/src/js_values/mod.rs index 37eb34ae..63fc7cf1 100644 --- a/napi/src/js_values/mod.rs +++ b/napi/src/js_values/mod.rs @@ -8,11 +8,13 @@ mod arraybuffer; mod boolean; mod buffer; mod class_property; +mod either; mod function; mod number; mod object; mod string; mod tagged_object; +mod undefined; mod value; mod value_ref; mod value_type; @@ -21,11 +23,13 @@ pub use arraybuffer::JsArrayBuffer; pub use boolean::JsBoolean; pub use buffer::JsBuffer; pub use class_property::Property; +pub use either::Either; pub use function::JsFunction; pub use number::JsNumber; pub use object::JsObject; pub use string::JsString; pub(crate) use tagged_object::TaggedObject; +pub use undefined::JsUndefined; pub(crate) use value::Value; pub(crate) use value_ref::Ref; pub use value_type::ValueType; @@ -34,9 +38,6 @@ pub use value_type::ValueType; #[derive(Clone, Copy, Debug)] pub struct JsUnknown(pub(crate) Value); -#[derive(Clone, Copy, Debug)] -pub struct JsUndefined(pub(crate) Value); - #[derive(Clone, Copy, Debug)] 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 { env, value, 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 { ($js_value:ident) => { impl $js_value { - pub fn into_raw(self) -> sys::napi_value { - self.0.value - } + #[inline] pub fn into_unknown(self) -> Result { JsUnknown::from_raw(self.0.env, self.0.value) } + + #[inline] pub fn coerce_to_number(self) -> Result { let mut new_raw_value = ptr::null_mut(); let status = @@ -113,6 +117,7 @@ macro_rules! impl_js_value_methods { value_type: ValueType::Number, })) } + #[inline] pub fn coerce_to_string(self) -> Result { let mut new_raw_value = ptr::null_mut(); let status = @@ -152,8 +157,6 @@ macro_rules! impl_js_value_methods { pub trait NapiValue: Sized { fn from_raw(env: sys::napi_env, value: sys::napi_value) -> Result; - fn from_raw_unchecked(env: sys::napi_env, value: sys::napi_value) -> Self; - fn raw_value(&self) -> sys::napi_value; } @@ -186,15 +189,11 @@ impl_napi_value_trait!(JsSymbol, Symbol); impl NapiValue for JsUnknown { fn from_raw(env: sys::napi_env, value: sys::napi_value) -> Result { - Ok(Self::from_raw_unchecked(env, value)) - } - - fn from_raw_unchecked(env: sys::napi_env, value: sys::napi_value) -> JsUnknown { - JsUnknown(Value { + Ok(JsUnknown(Value { env, value, value_type: Unknown, - }) + })) } fn raw_value(&self) -> sys::napi_value { diff --git a/napi/src/js_values/string.rs b/napi/src/js_values/string.rs index aa523584..c17843da 100644 --- a/napi/src/js_values/string.rs +++ b/napi/src/js_values/string.rs @@ -7,7 +7,7 @@ use std::str; use super::Value; use crate::error::check_status; -use crate::{sys, Error, JsNumber, NapiValue, Result, Status}; +use crate::{sys, Error, JsNumber, Result, Status}; #[derive(Clone, Copy, Debug)] pub struct JsString(pub(crate) Value); diff --git a/napi/src/js_values/undefined.rs b/napi/src/js_values/undefined.rs new file mode 100644 index 00000000..adafb5d0 --- /dev/null +++ b/napi/src/js_values/undefined.rs @@ -0,0 +1,4 @@ +use super::Value; + +#[derive(Clone, Copy, Debug)] +pub struct JsUndefined(pub(crate) Value); diff --git a/napi/src/lib.rs b/napi/src/lib.rs index 2df52218..b34fc603 100644 --- a/napi/src/lib.rs +++ b/napi/src/lib.rs @@ -159,7 +159,7 @@ macro_rules! register_module { let hook_result = Ok(()); match hook_result.and_then(move |_| result) { - Ok(_) => exports.into_raw(), + Ok(_) => exports.raw_value(), Err(e) => { unsafe { sys::napi_throw_error( diff --git a/napi/src/threadsafe_function.rs b/napi/src/threadsafe_function.rs index 8e4ac1f3..4a84dae4 100644 --- a/napi/src/threadsafe_function.rs +++ b/napi/src/threadsafe_function.rs @@ -207,9 +207,9 @@ unsafe extern "C" fn call_js_cb( let values = ret.unwrap(); let js_null = env.get_null().unwrap(); let mut raw_values: Vec = vec![]; - raw_values.push(js_null.into_raw()); + raw_values.push(js_null.0.value); for item in values.iter() { - raw_values.push(item.into_raw()) + raw_values.push(item.0.value) } status = sys::napi_call_function( diff --git a/test_module/__test__/either.spec.js b/test_module/__test__/either.spec.js new file mode 100644 index 00000000..ce9cd522 --- /dev/null +++ b/test_module/__test__/either.spec.js @@ -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) +}) diff --git a/test_module/src/either.rs b/test_module/src/either.rs new file mode 100644 index 00000000..530bfbd5 --- /dev/null +++ b/test_module/src/either.rs @@ -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> { + let arg = ctx.get::>(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 { + let value: Option = ctx.try_get::(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) + } +} diff --git a/test_module/src/lib.rs b/test_module/src/lib.rs index b0711945..88297dd5 100644 --- a/test_module/src/lib.rs +++ b/test_module/src/lib.rs @@ -15,6 +15,7 @@ mod napi5; mod tokio_rt; mod buffer; +mod either; mod external; mod function; mod napi_version; @@ -22,6 +23,7 @@ mod symbol; mod task; use buffer::{buffer_to_string, get_buffer_length}; +use either::{dynamic_argument_length, either_number_string}; use external::{create_external, get_external_count}; use function::{call_function, call_function_with_this}; #[cfg(napi4)] @@ -52,6 +54,8 @@ fn init(module: &mut Module) -> Result<()> { module.create_named_method("getNapiVersion", get_napi_version)?; module.create_named_method("testCallFunction", call_function)?; 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)] module.create_named_method("testExecuteTokioReadfile", test_execute_tokio_readfile)?; #[cfg(napi4)]