feat(napi): impl BufferSlice and Uint8ClampedSlice (#1979)
This commit is contained in:
parent
f88a041fa3
commit
8ca1967bd8
9 changed files with 347 additions and 4 deletions
|
@ -10,7 +10,7 @@ use std::sync::{
|
|||
#[cfg(all(feature = "napi4", not(feature = "noop"), not(target_family = "wasm")))]
|
||||
use crate::bindgen_prelude::{CUSTOM_GC_TSFN, CUSTOM_GC_TSFN_DESTROYED, THREADS_CAN_ACCESS_ENV};
|
||||
pub use crate::js_values::TypedArrayType;
|
||||
use crate::{check_status, sys, Error, Result, Status};
|
||||
use crate::{check_status, sys, Error, Result, Status, ValueType};
|
||||
|
||||
use super::{FromNapiValue, ToNapiValue, TypeName, ValidateNapiValue};
|
||||
|
||||
|
@ -480,6 +480,27 @@ macro_rules! impl_from_slice {
|
|||
},
|
||||
"Get TypedArray info failed"
|
||||
)?;
|
||||
if typed_array_type != $typed_array_type as i32 {
|
||||
return Err(Error::new(
|
||||
Status::InvalidArg,
|
||||
format!("Expected $name, got {}", typed_array_type),
|
||||
));
|
||||
}
|
||||
Ok(if length == 0 {
|
||||
&mut []
|
||||
} else {
|
||||
unsafe { core::slice::from_raw_parts_mut(data as *mut $rust_type, length) }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FromNapiValue for &[$rust_type] {
|
||||
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
|
||||
let mut typed_array_type = 0;
|
||||
let mut length = 0;
|
||||
let mut data = ptr::null_mut();
|
||||
let mut array_buffer = ptr::null_mut();
|
||||
let mut byte_offset = 0;
|
||||
check_status!(
|
||||
unsafe {
|
||||
sys::napi_get_typedarray_info(
|
||||
|
@ -500,7 +521,65 @@ macro_rules! impl_from_slice {
|
|||
format!("Expected $name, got {}", typed_array_type),
|
||||
));
|
||||
}
|
||||
Ok(unsafe { core::slice::from_raw_parts_mut(data as *mut $rust_type, length) })
|
||||
Ok(if length == 0 {
|
||||
&[]
|
||||
} else {
|
||||
unsafe { core::slice::from_raw_parts_mut(data as *mut $rust_type, length) }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeName for &mut [$rust_type] {
|
||||
fn type_name() -> &'static str {
|
||||
concat!("TypedArray<", stringify!($rust_type), ">")
|
||||
}
|
||||
|
||||
fn value_type() -> crate::ValueType {
|
||||
crate::ValueType::Object
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeName for &[$rust_type] {
|
||||
fn type_name() -> &'static str {
|
||||
concat!("TypedArray<", stringify!($rust_type), ">")
|
||||
}
|
||||
|
||||
fn value_type() -> crate::ValueType {
|
||||
crate::ValueType::Object
|
||||
}
|
||||
}
|
||||
|
||||
impl ValidateNapiValue for &[$rust_type] {
|
||||
unsafe fn validate(env: sys::napi_env, napi_val: sys::napi_value) -> Result<sys::napi_value> {
|
||||
let mut is_typed_array = false;
|
||||
check_status!(
|
||||
unsafe { sys::napi_is_typedarray(env, napi_val, &mut is_typed_array) },
|
||||
"Failed to validate napi typed array"
|
||||
)?;
|
||||
if !is_typed_array {
|
||||
return Err(Error::new(
|
||||
Status::InvalidArg,
|
||||
"Expected a TypedArray value".to_owned(),
|
||||
));
|
||||
}
|
||||
Ok(ptr::null_mut())
|
||||
}
|
||||
}
|
||||
|
||||
impl ValidateNapiValue for &mut [$rust_type] {
|
||||
unsafe fn validate(env: sys::napi_env, napi_val: sys::napi_value) -> Result<sys::napi_value> {
|
||||
let mut is_typed_array = false;
|
||||
check_status!(
|
||||
unsafe { sys::napi_is_typedarray(env, napi_val, &mut is_typed_array) },
|
||||
"Failed to validate napi typed array"
|
||||
)?;
|
||||
if !is_typed_array {
|
||||
return Err(Error::new(
|
||||
Status::InvalidArg,
|
||||
"Expected a TypedArray value".to_owned(),
|
||||
));
|
||||
}
|
||||
Ok(ptr::null_mut())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -568,6 +647,106 @@ impl_typed_array!(BigUint64Array, u64, TypedArrayType::BigUint64);
|
|||
#[cfg(feature = "napi6")]
|
||||
impl_from_slice!(BigUint64Array, u64, TypedArrayType::BigUint64);
|
||||
|
||||
/// Zero copy Uint8ClampedArray slice shared between Rust and Node.js.
|
||||
/// It can only be used in non-async context and the lifetime is bound to the fn closure.
|
||||
/// If you want to use Node.js `Uint8ClampedArray` in async context or want to extend the lifetime, use `Uint8ClampedArray` instead.
|
||||
pub struct Uint8ClampedSlice<'scope> {
|
||||
pub(crate) inner: &'scope mut [u8],
|
||||
raw_value: sys::napi_value,
|
||||
}
|
||||
|
||||
impl<'scope> FromNapiValue for Uint8ClampedSlice<'scope> {
|
||||
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
|
||||
let mut typed_array_type = 0;
|
||||
let mut length = 0;
|
||||
let mut data = ptr::null_mut();
|
||||
let mut array_buffer = ptr::null_mut();
|
||||
let mut byte_offset = 0;
|
||||
check_status!(
|
||||
unsafe {
|
||||
sys::napi_get_typedarray_info(
|
||||
env,
|
||||
napi_val,
|
||||
&mut typed_array_type,
|
||||
&mut length,
|
||||
&mut data,
|
||||
&mut array_buffer,
|
||||
&mut byte_offset,
|
||||
)
|
||||
},
|
||||
"Get TypedArray info failed"
|
||||
)?;
|
||||
if typed_array_type != TypedArrayType::Uint8Clamped as i32 {
|
||||
return Err(Error::new(
|
||||
Status::InvalidArg,
|
||||
format!("Expected $name, got {}", typed_array_type),
|
||||
));
|
||||
}
|
||||
Ok(Self {
|
||||
inner: if length == 0 {
|
||||
&mut []
|
||||
} else {
|
||||
unsafe { core::slice::from_raw_parts_mut(data.cast(), length) }
|
||||
},
|
||||
raw_value: napi_val,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ToNapiValue for Uint8ClampedSlice<'_> {
|
||||
#[allow(unused_variables)]
|
||||
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
|
||||
Ok(val.raw_value)
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeName for Uint8ClampedSlice<'_> {
|
||||
fn type_name() -> &'static str {
|
||||
"Uint8ClampedArray"
|
||||
}
|
||||
|
||||
fn value_type() -> ValueType {
|
||||
ValueType::Object
|
||||
}
|
||||
}
|
||||
|
||||
impl ValidateNapiValue for Uint8ClampedSlice<'_> {
|
||||
unsafe fn validate(env: sys::napi_env, napi_val: sys::napi_value) -> Result<sys::napi_value> {
|
||||
let mut is_typedarray = false;
|
||||
check_status!(
|
||||
unsafe { sys::napi_is_typedarray(env, napi_val, &mut is_typedarray) },
|
||||
"Failed to validate typed buffer"
|
||||
)?;
|
||||
if !is_typedarray {
|
||||
return Err(Error::new(
|
||||
Status::InvalidArg,
|
||||
"Expected a TypedArray value".to_owned(),
|
||||
));
|
||||
}
|
||||
Ok(ptr::null_mut())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for Uint8ClampedSlice<'_> {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<'scope> Deref for Uint8ClampedSlice<'scope> {
|
||||
type Target = [u8];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<'scope> DerefMut for Uint8ClampedSlice<'scope> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<Vec<u8>>> From<T> for Uint8Array {
|
||||
fn from(data: T) -> Self {
|
||||
Uint8Array::new(data.into())
|
||||
|
|
|
@ -18,7 +18,98 @@ thread_local! {
|
|||
pub (crate) static BUFFER_DATA: Mutex<HashSet<*mut u8>> = Default::default();
|
||||
}
|
||||
|
||||
/// Zero copy buffer slice shared between Rust and Node.js.
|
||||
/// It can only be used in non-async context and the lifetime is bound to the fn closure.
|
||||
/// If you want to use Node.js Buffer in async context or want to extend the lifetime, use `Buffer` instead.
|
||||
pub struct BufferSlice<'scope> {
|
||||
pub(crate) inner: &'scope mut [u8],
|
||||
raw_value: sys::napi_value,
|
||||
}
|
||||
|
||||
impl<'scope> FromNapiValue for BufferSlice<'scope> {
|
||||
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
|
||||
let mut buf = ptr::null_mut();
|
||||
let mut len = 0usize;
|
||||
check_status!(
|
||||
unsafe { sys::napi_get_buffer_info(env, napi_val, &mut buf, &mut len) },
|
||||
"Failed to get Buffer pointer and length"
|
||||
)?;
|
||||
// From the docs of `napi_get_buffer_info`:
|
||||
// > [out] data: The underlying data buffer of the node::Buffer. If length is 0, this may be
|
||||
// > NULL or any other pointer value.
|
||||
//
|
||||
// In order to guarantee that `slice::from_raw_parts` is sound, the pointer must be non-null, so
|
||||
// let's make sure it always is, even in the case of `napi_get_buffer_info` returning a null
|
||||
// ptr.
|
||||
Ok(Self {
|
||||
inner: if len == 0 {
|
||||
&mut []
|
||||
} else {
|
||||
unsafe { slice::from_raw_parts_mut(buf.cast(), len) }
|
||||
},
|
||||
raw_value: napi_val,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ToNapiValue for BufferSlice<'_> {
|
||||
#[allow(unused_variables)]
|
||||
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
|
||||
Ok(val.raw_value)
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeName for BufferSlice<'_> {
|
||||
fn type_name() -> &'static str {
|
||||
"Buffer"
|
||||
}
|
||||
|
||||
fn value_type() -> ValueType {
|
||||
ValueType::Object
|
||||
}
|
||||
}
|
||||
|
||||
impl ValidateNapiValue for BufferSlice<'_> {
|
||||
unsafe fn validate(env: sys::napi_env, napi_val: sys::napi_value) -> Result<sys::napi_value> {
|
||||
let mut is_buffer = false;
|
||||
check_status!(
|
||||
unsafe { sys::napi_is_buffer(env, napi_val, &mut is_buffer) },
|
||||
"Failed to validate napi buffer"
|
||||
)?;
|
||||
if !is_buffer {
|
||||
return Err(Error::new(
|
||||
Status::InvalidArg,
|
||||
"Expected a Buffer value".to_owned(),
|
||||
));
|
||||
}
|
||||
Ok(ptr::null_mut())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for BufferSlice<'_> {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<'scope> Deref for BufferSlice<'scope> {
|
||||
type Target = [u8];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<'scope> DerefMut for BufferSlice<'scope> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
/// Zero copy u8 vector shared between rust and napi.
|
||||
/// It's designed to be used in `async` context, so it contains overhead to ensure the underlying data is not dropped.
|
||||
/// For non-async context, use `BufferRef` instead.
|
||||
///
|
||||
/// Auto reference the raw JavaScript value, and release it when dropped.
|
||||
/// So it is safe to use it in `async fn`, the `&[u8]` under the hood will not be dropped until the `drop` called.
|
||||
/// Clone will create a new `Reference` to the same underlying `JavaScript Buffer`.
|
||||
|
|
|
@ -255,6 +255,10 @@ Generated by [AVA](https://avajs.dev).
|
|||
␊
|
||||
export function acceptThreadsafeFunctionTupleArgs(func: (err: Error | null, arg0: number, arg1: boolean, arg2: string) => any): void␊
|
||||
␊
|
||||
export function acceptUint8ClampedSlice(input: Uint8ClampedArray): bigint␊
|
||||
␊
|
||||
export function acceptUint8ClampedSliceAndBufferSlice(a: Buffer, b: Uint8ClampedArray): bigint␊
|
||||
␊
|
||||
export function add(a: number, b: number): number␊
|
||||
␊
|
||||
export const enum ALIAS {␊
|
||||
|
@ -672,6 +676,8 @@ Generated by [AVA](https://avajs.dev).
|
|||
␊
|
||||
export function validateBuffer(b: Buffer): number␊
|
||||
␊
|
||||
export function validateBufferSlice(input: Buffer): number␊
|
||||
␊
|
||||
export function validateDate(d: Date): number␊
|
||||
␊
|
||||
export function validateDateTime(d: Date): number␊
|
||||
|
@ -696,6 +702,10 @@ Generated by [AVA](https://avajs.dev).
|
|||
␊
|
||||
export function validateTypedArray(input: Uint8Array): number␊
|
||||
␊
|
||||
export function validateTypedArraySlice(input: Uint8Array): number␊
|
||||
␊
|
||||
export function validateUint8ClampedSlice(input: Uint8ClampedArray): number␊
|
||||
␊
|
||||
export function validateUndefined(i: undefined): boolean␊
|
||||
␊
|
||||
export function withAbortController(a: number, b: number, signal: AbortSignal): Promise<number>␊
|
||||
|
|
Binary file not shown.
|
@ -3,6 +3,8 @@ import test from 'ava'
|
|||
const {
|
||||
validateArray,
|
||||
validateTypedArray,
|
||||
validateTypedArraySlice,
|
||||
validateBufferSlice,
|
||||
validateBigint,
|
||||
validateBuffer,
|
||||
validateBoolean,
|
||||
|
@ -38,6 +40,21 @@ test('should validate arraybuffer', (t) => {
|
|||
code: 'InvalidArg',
|
||||
message: 'Expected a TypedArray value',
|
||||
})
|
||||
|
||||
t.is(validateTypedArraySlice(new Uint8Array([1, 2, 3])), 3)
|
||||
|
||||
// @ts-expect-error
|
||||
t.throws(() => validateTypedArraySlice(1), {
|
||||
code: 'InvalidArg',
|
||||
message: 'Expected a TypedArray value',
|
||||
})
|
||||
|
||||
t.is(validateBufferSlice(Buffer.from('hello')), 5)
|
||||
// @ts-expect-error
|
||||
t.throws(() => validateBufferSlice(2), {
|
||||
code: 'InvalidArg',
|
||||
message: 'Expected a Buffer value',
|
||||
})
|
||||
})
|
||||
|
||||
test('should validate BigInt', (t) => {
|
||||
|
@ -123,7 +140,7 @@ test('should validate Map', (t) => {
|
|||
})
|
||||
})
|
||||
|
||||
test.only('should validate promise', async (t) => {
|
||||
test('should validate promise', async (t) => {
|
||||
t.is(
|
||||
await validatePromise(
|
||||
new Promise((resolve) => {
|
||||
|
|
|
@ -4,7 +4,7 @@ import { fileURLToPath } from 'node:url'
|
|||
|
||||
import { spy } from 'sinon'
|
||||
|
||||
import type { AliasedStruct, Animal as AnimalClass } from '../index.js'
|
||||
import { type AliasedStruct, type Animal as AnimalClass } from '../index.js'
|
||||
|
||||
import { test } from './test.framework.js'
|
||||
|
||||
|
@ -105,6 +105,8 @@ const {
|
|||
i64ArrayToArray,
|
||||
f32ArrayToArray,
|
||||
f64ArrayToArray,
|
||||
acceptUint8ClampedSlice,
|
||||
acceptUint8ClampedSliceAndBufferSlice,
|
||||
convertU32Array,
|
||||
createExternalTypedArray,
|
||||
mutateTypedArray,
|
||||
|
@ -694,6 +696,15 @@ test('TypedArray', (t) => {
|
|||
const bird = new Bird('Carolyn')
|
||||
|
||||
t.is(bird.acceptSliceMethod(new Uint8Array([1, 2, 3])), 3)
|
||||
|
||||
t.is(acceptUint8ClampedSlice(new Uint8ClampedArray([1, 2, 3])), 3n)
|
||||
t.is(
|
||||
acceptUint8ClampedSliceAndBufferSlice(
|
||||
Buffer.from([1, 2, 3]),
|
||||
new Uint8ClampedArray([1, 2, 3]),
|
||||
),
|
||||
6n,
|
||||
)
|
||||
})
|
||||
|
||||
test('reset empty buffer', (t) => {
|
||||
|
|
10
examples/napi/index.d.ts
vendored
10
examples/napi/index.d.ts
vendored
|
@ -245,6 +245,10 @@ export function acceptThreadsafeFunctionFatal(func: (arg: number) => any): void
|
|||
|
||||
export function acceptThreadsafeFunctionTupleArgs(func: (err: Error | null, arg0: number, arg1: boolean, arg2: string) => any): void
|
||||
|
||||
export function acceptUint8ClampedSlice(input: Uint8ClampedArray): bigint
|
||||
|
||||
export function acceptUint8ClampedSliceAndBufferSlice(a: Buffer, b: Uint8ClampedArray): bigint
|
||||
|
||||
export function add(a: number, b: number): number
|
||||
|
||||
export const enum ALIAS {
|
||||
|
@ -662,6 +666,8 @@ export function validateBoolean(i: boolean): boolean
|
|||
|
||||
export function validateBuffer(b: Buffer): number
|
||||
|
||||
export function validateBufferSlice(input: Buffer): number
|
||||
|
||||
export function validateDate(d: Date): number
|
||||
|
||||
export function validateDateTime(d: Date): number
|
||||
|
@ -686,6 +692,10 @@ export function validateSymbol(s: symbol): boolean
|
|||
|
||||
export function validateTypedArray(input: Uint8Array): number
|
||||
|
||||
export function validateTypedArraySlice(input: Uint8Array): number
|
||||
|
||||
export function validateUint8ClampedSlice(input: Uint8ClampedArray): number
|
||||
|
||||
export function validateUndefined(i: undefined): boolean
|
||||
|
||||
export function withAbortController(a: number, b: number, signal: AbortSignal): Promise<number>
|
||||
|
|
|
@ -18,6 +18,21 @@ fn validate_typed_array(input: Uint8Array) -> u32 {
|
|||
input.len() as u32
|
||||
}
|
||||
|
||||
#[napi(strict)]
|
||||
fn validate_typed_array_slice(input: &[u8]) -> u32 {
|
||||
input.len() as u32
|
||||
}
|
||||
|
||||
#[napi(strict)]
|
||||
fn validate_uint8_clamped_slice(input: Uint8ClampedSlice) -> u32 {
|
||||
input.len() as u32
|
||||
}
|
||||
|
||||
#[napi(strict)]
|
||||
fn validate_buffer_slice(input: BufferSlice) -> u32 {
|
||||
input.len() as u32
|
||||
}
|
||||
|
||||
#[napi(strict)]
|
||||
fn validate_bigint(input: BigInt) -> i128 {
|
||||
input.get_i128().0
|
||||
|
|
|
@ -104,6 +104,16 @@ fn i64_array_to_array(input: &[i64]) -> Vec<i64> {
|
|||
input.to_vec()
|
||||
}
|
||||
|
||||
#[napi]
|
||||
fn accept_uint8_clamped_slice(input: Uint8ClampedSlice) -> usize {
|
||||
input.len()
|
||||
}
|
||||
|
||||
#[napi]
|
||||
fn accept_uint8_clamped_slice_and_buffer_slice(a: BufferSlice, b: Uint8ClampedSlice) -> usize {
|
||||
a.len() + b.len()
|
||||
}
|
||||
|
||||
struct AsyncBuffer {
|
||||
buf: Buffer,
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue