Skip to content

Commit

Permalink
Extractors
Browse files Browse the repository at this point in the history
  • Loading branch information
kjvalencik committed Feb 23, 2024
1 parent 50c74c0 commit 470ed1f
Show file tree
Hide file tree
Showing 12 changed files with 666 additions and 35 deletions.
80 changes: 80 additions & 0 deletions crates/neon/src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ use crate::{
types::{
boxed::{Finalize, JsBox},
error::JsError,
extract::FromArgs,
private::ValueInternal,
Deferred, JsArray, JsArrayBuffer, JsBoolean, JsBuffer, JsFunction, JsNull, JsNumber,
JsObject, JsPromise, JsString, JsUndefined, JsValue, StringResult, Value,
Expand Down Expand Up @@ -212,6 +213,36 @@ impl CallbackInfo<'_> {
local
}
}

pub(crate) fn argv_exact<'b, C: Context<'b>, const N: usize>(
&self,
cx: &mut C,
) -> [Handle<'b, JsValue>; N] {
use std::ptr;

let mut argv = [JsValue::new_internal(ptr::null_mut()); N];
let mut argc = argv.len();

// # Safety
// * Node-API fills empty slots with `undefined
// * `Handle` and `JsValue` are transparent wrappers around a raw pointer
unsafe {
assert_eq!(
sys::get_cb_info(
cx.env().to_raw(),
self.info,
&mut argc,
argv.as_mut_ptr().cast(),
ptr::null_mut(),
ptr::null_mut(),
),
sys::Status::Ok,
);
}

// Empty values will be filled with `undefined`
argv
}
}

/// Indicates whether a function was called with `new`.
Expand Down Expand Up @@ -683,6 +714,55 @@ impl<'a> FunctionContext<'a> {
pub fn this_value(&mut self) -> Handle<'a, JsValue> {
JsValue::new_internal(self.info.this(self))
}

/// Extract Rust data from the JavaScript arguments.
///
/// This is frequently more efficient and ergonomic than getting arguments
/// individually. See the [`extract`](crate::types::extract) module documentation
/// for more examples.
///
/// ```
/// # use neon::{prelude::*, types::extract::*};
/// fn add(mut cx: FunctionContext) -> JsResult<JsNumber> {
/// let (number(a), number(b)) = cx.args()?;
///
/// Ok(cx.number(a + b))
/// }
/// ```
pub fn args<T>(&mut self) -> NeonResult<T>
where
T: FromArgs<'a>,
{
T::from_args(self)
}

/// Extract Rust data from the JavaScript arguments.
///
/// Similar to [`FunctionContext::args`], but does not throw a JavaScript exception on errors. Useful
/// for function overloading.
///
/// ```
/// # use neon::{prelude::*, types::extract::*};
/// fn combine(mut cx: FunctionContext) -> JsResult<JsValue> {
/// if let Some((number(a), number(b))) = cx.args_opt()? {
/// return Ok(cx.number(a + b).upcast());
/// }
///
/// let (string(a), string(b)) = cx.args()?;
///
/// Ok(cx.string(a + &b).upcast())
/// }
/// ```
pub fn args_opt<T>(&mut self) -> NeonResult<Option<T>>
where
T: FromArgs<'a>,
{
T::from_args_opt(self)
}

pub(crate) fn argv<const N: usize>(&mut self) -> [Handle<'a, JsValue>; N] {
self.info.argv_exact(self)
}
}

impl<'a> ContextInternal<'a> for FunctionContext<'a> {
Expand Down
2 changes: 2 additions & 0 deletions crates/neon/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@
//! [supported]: https://github.com/neon-bindings/neon#platform-support
#![cfg_attr(docsrs, feature(doc_cfg))]

extern crate core;

pub mod context;
pub mod event;
pub mod handle;
Expand Down
4 changes: 2 additions & 2 deletions crates/neon/src/types_impl/bigint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -428,8 +428,8 @@ unsafe impl TransparentNoCopyWrapper for JsBigInt {
}

impl private::ValueInternal for JsBigInt {
fn name() -> String {
"BigInt".to_string()
fn name() -> &'static str {
"BigInt"
}

fn is_typeof<Other: Value>(env: Env, other: &Other) -> bool {
Expand Down
4 changes: 2 additions & 2 deletions crates/neon/src/types_impl/boxed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,8 @@ unsafe impl<T: 'static> TransparentNoCopyWrapper for JsBox<T> {
}

impl<T: 'static> ValueInternal for JsBox<T> {
fn name() -> String {
any::type_name::<Self>().to_string()
fn name() -> &'static str {
any::type_name::<Self>()
}

fn is_typeof<Other: Value>(env: Env, other: &Other) -> bool {
Expand Down
12 changes: 6 additions & 6 deletions crates/neon/src/types_impl/buffer/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@ unsafe impl TransparentNoCopyWrapper for JsBuffer {
}

impl ValueInternal for JsBuffer {
fn name() -> String {
"Buffer".to_string()
fn name() -> &'static str {
"Buffer"
}

fn is_typeof<Other: Value>(env: Env, other: &Other) -> bool {
Expand Down Expand Up @@ -341,8 +341,8 @@ unsafe impl TransparentNoCopyWrapper for JsArrayBuffer {
}

impl ValueInternal for JsArrayBuffer {
fn name() -> String {
"JsArrayBuffer".to_string()
fn name() -> &'static str {
"JsArrayBuffer"
}

fn is_typeof<Other: Value>(env: Env, other: &Other) -> bool {
Expand Down Expand Up @@ -791,8 +791,8 @@ macro_rules! impl_typed_array {
impl Object for JsTypedArray<$etyp> {}

impl ValueInternal for JsTypedArray<$etyp> {
fn name() -> String {
stringify!($typ).to_string()
fn name() -> &'static str {
stringify!($typ)
}

fn is_typeof<Other: Value>(env: Env, other: &Other) -> bool {
Expand Down
4 changes: 2 additions & 2 deletions crates/neon/src/types_impl/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,8 @@ impl JsDate {
}

impl ValueInternal for JsDate {
fn name() -> String {
"object".to_string()
fn name() -> &'static str {
"object"
}

fn is_typeof<Other: Value>(env: Env, other: &Other) -> bool {
Expand Down
4 changes: 2 additions & 2 deletions crates/neon/src/types_impl/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ unsafe impl TransparentNoCopyWrapper for JsError {
}

impl ValueInternal for JsError {
fn name() -> String {
"Error".to_string()
fn name() -> &'static str {
"Error"
}

fn is_typeof<Other: Value>(env: Env, other: &Other) -> bool {
Expand Down
201 changes: 201 additions & 0 deletions crates/neon/src/types_impl/extract/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
//! Traits and utilities for extract Rust data from JavaScript values
//!
//! The full list of included extractors can be found on [`TryFromJs`].
//!
//! ## Extracting Handles
//!
//! JavaScript arguments may be extracted into a Rust tuple.
//!
//! ```
//! # use neon::{prelude::*, types::extract::*};
//! fn greet(mut cx: FunctionContext) -> JsResult<JsString> {
//! let (greeting, name): (Handle<JsString>, Handle<JsString>) = cx.args()?;
//! let message = format!("{}, {}!", greeting.value(&mut cx), name.value(&mut cx));
//!
//! Ok(cx.string(message))
//! }
//! ```
//!
//! ## Extracting Native Types
//!
//! It's also possible to extract directly into native Rust types instead of a [`Handle`].
//!
//! ```
//! # use neon::{prelude::*, types::extract::*};
//! fn add(mut cx: FunctionContext) -> JsResult<JsNumber> {
//! let (number(a), number(b)) = cx.args()?;
//!
//! Ok(cx.number(a + b))
//! }
//! ```
//!
//! ## Extracting [`Option`]
//!
//! It's also possible to mix [`Handle`], Rust types, and even [`Option`] for
//! handling `null` and `undefined`.
//!
//! ```
//! # use neon::{prelude::*, types::extract::*};
//! fn get_or_default(mut cx: FunctionContext) -> JsResult<JsValue> {
//! let (Opt::<f64>(n), Val::<JsValue>(default_value)) = cx.args()?;
//!
//! if let Some(n) = n {
//! return Ok(cx.number(n).upcast());
//! }
//!
//! Ok(default_value)
//! }
//! ```
//!
//! ## Additional Extractors
//!
//! In some cases, the expected JavaScript type is ambiguous. For example, when
//! trying to extract an [`f64`], the argument may be a `Date` instead of a `number`.
//! Newtype extractors are provided to help.
//!
//! ```
//! # use neon::{prelude::*, types::extract::*};
//! # #[cfg(feature = "napi-5")]
//! # use neon::types::JsDate;
//!
//! # #[cfg(feature = "napi-5")]
//! fn add_hours(mut cx: FunctionContext) -> JsResult<JsDate> {
//! const MS_PER_HOUR: f64 = 60.0 * 60.0 * 1000.0;
//!
//! let (Date(date), number(hours)) = cx.args()?;
//! let date = date + hours * MS_PER_HOUR;
//!
//! cx.date(date).or_throw(&mut cx)
//! }
//! ```
//!
//! ## Overloaded Functions
//!
//! It's common in JavaScript to overload function signatures. This can be implemented with
//! [`FunctionContext::args_opt`] or [`Context::try_catch`].
//!
//! ```
//! # use neon::{prelude::*, types::extract::*};
//!
//! fn add(mut cx: FunctionContext, a: f64, b: f64) -> Handle<JsNumber> {
//! cx.number(a + b)
//! }
//!
//! fn concat(mut cx: FunctionContext, a: String, b: String) -> Handle<JsString> {
//! cx.string(a + &b)
//! }
//!
//! fn combine(mut cx: FunctionContext) -> JsResult<JsValue> {
//! if let Some((a, b)) = cx.args_opt()? {
//! return Ok(add(cx, a, b).upcast());
//! }
//!
//! let (a, b) = cx.args()?;
//!
//! Ok(concat(cx, a, b).upcast())
//! }
//! ```
//!
//! Note well, in this example, type annotations are not required on the tuple because
//! Rust is able to infer it from the type arguments on `add` and `concat`.

use crate::{
context::{Context, FunctionContext},
handle::Handle,
result::NeonResult,
types::JsValue,
};

pub use self::types::*;

mod types;

/// Extract Rust data from a JavaScript value
pub trait TryFromJs<'cx>: private::Sealed + Sized {
type Error;

fn try_from_js<C>(cx: &mut C, v: Handle<'cx, JsValue>) -> NeonResult<Result<Self, Self::Error>>
where
C: Context<'cx>;

fn from_js<C>(cx: &mut C, v: Handle<'cx, JsValue>) -> NeonResult<Self>
where
C: Context<'cx>;
}

/// Trait specifying values that may be extracted from function arguments.
///
/// **Note:** This trait is implemented for tuples of up to 32 values, but for
/// the sake of brevity, only tuples up to size 8 are shown in this documentation.
pub trait FromArgs<'cx>: private::FromArgsInternal<'cx> {}

macro_rules! impl_arguments {
($(#[$attrs:meta])? [$($head:ident),*], []) => {};

($(#[$attrs:meta])? [$($head:ident),*], [$cur:ident $(, $tail:ident)*]) => {
$(#[$attrs])?
impl<'cx, $($head,)* $cur> FromArgs<'cx> for ($($head,)* $cur,)
where
$($head: TryFromJs<'cx>,)*
$cur: TryFromJs<'cx>,
{}

impl<'cx, $($head,)* $cur> private::FromArgsInternal<'cx> for ($($head,)* $cur,)
where
$($head: TryFromJs<'cx>,)*
$cur: TryFromJs<'cx>,
{
fn from_args(cx: &mut FunctionContext<'cx>) -> NeonResult<Self> {
#[allow(non_snake_case)]
let [$($head,)* $cur] = cx.argv();

Ok((
$($head::from_js(cx, $head)?,)*
$cur::from_js(cx, $cur)?,
))
}

fn from_args_opt(cx: &mut FunctionContext<'cx>) -> NeonResult<Option<Self>> {
#[allow(non_snake_case)]
let [$($head,)* $cur] = cx.argv();

Ok(Some((
$(
match $head::try_from_js(cx, $head)? {
Ok(v) => v,
Err(_) => return Ok(None),
},
)*
match $cur::try_from_js(cx, $cur)? {
Ok(v) => v,
Err(_) => return Ok(None),
},
)))
}
}

impl_arguments!($(#[$attrs])? [$($head,)* $cur], [$($tail),*]);
};
}

impl_arguments!([], [T1, T2, T3, T4, T5, T6, T7, T8]);
impl_arguments!(
#[doc(hidden)]
[T1, T2, T3, T4, T5, T6, T7, T8],
[
T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26,
T27, T28, T29, T30, T31, T32
]
);

mod private {
use crate::{context::FunctionContext, result::NeonResult};

pub trait Sealed {}

pub trait FromArgsInternal<'cx>: Sized {
fn from_args(cx: &mut FunctionContext<'cx>) -> NeonResult<Self>;

fn from_args_opt(cx: &mut FunctionContext<'cx>) -> NeonResult<Option<Self>>;
}
}
Loading

0 comments on commit 470ed1f

Please sign in to comment.