diff --git a/crates/napi/src/bindgen_runtime/js_values/buffer.rs b/crates/napi/src/bindgen_runtime/js_values/buffer.rs index d1c7a1bc..db5233cd 100644 --- a/crates/napi/src/bindgen_runtime/js_values/buffer.rs +++ b/crates/napi/src/bindgen_runtime/js_values/buffer.rs @@ -3,15 +3,33 @@ use std::{mem, ptr}; use crate::{bindgen_prelude::*, check_status, sys, Result, ValueType}; -/// zero copy u8 vector shared between rust and napi +/// Zero copy u8 vector shared between rust and napi. +/// 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. pub struct Buffer { inner: mem::ManuallyDrop>, + raw: Option<(sys::napi_ref, sys::napi_env)>, } +impl Drop for Buffer { + fn drop(&mut self) { + if let Some((ref_, env)) = self.raw { + check_status_or_throw!( + env, + unsafe { sys::napi_delete_reference(env, ref_) }, + "Failed to delete Buffer reference in drop" + ); + } + } +} + +unsafe impl Send for Buffer {} + impl From> for Buffer { fn from(data: Vec) -> Self { Buffer { inner: mem::ManuallyDrop::new(data), + raw: None, } } } @@ -68,7 +86,11 @@ impl FromNapiValue for Buffer { unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { let mut buf = ptr::null_mut(); let mut len = 0; - + let mut ref_ = ptr::null_mut(); + check_status!( + unsafe { sys::napi_create_reference(env, napi_val, 1, &mut ref_) }, + "Failed to create reference from Buffer" + )?; check_status!( unsafe { sys::napi_get_buffer_info(env, napi_val, &mut buf, &mut len as *mut usize) }, "Failed to convert napi buffer into rust Vec" @@ -76,12 +98,27 @@ impl FromNapiValue for Buffer { Ok(Self { inner: mem::ManuallyDrop::new(unsafe { Vec::from_raw_parts(buf as *mut _, len, len) }), + raw: Some((ref_, env)), }) } } impl ToNapiValue for Buffer { unsafe fn to_napi_value(env: sys::napi_env, mut val: Self) -> Result { + // From Node.js value, not from `Vec` + if let Some((ref_, _)) = val.raw { + let mut buf = ptr::null_mut(); + check_status!( + unsafe { sys::napi_get_reference_value(env, ref_, &mut buf) }, + "Failed to get Buffer value from reference" + )?; + check_status!( + unsafe { sys::napi_delete_reference(env, ref_) }, + "Failed to delete Buffer reference" + )?; + val.raw = None; // Prevent double free + return Ok(buf); + } let len = val.inner.len(); let mut ret = ptr::null_mut(); check_status!( diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index 4e6eb413..f7f6738f 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -152,6 +152,7 @@ Generated by [AVA](https://avajs.dev). export function createExternalTypedArray(): Uint32Array␊ export function mutateTypedArray(input: Float32Array): void␊ export function derefUint8Array(a: Uint8Array, b: Uint8ClampedArray): number␊ + export function bufferPassThrough(buf: Buffer): Promise␊ /**␊ * \`constructor\` option for \`struct\` requires all fields to be public,␊ * otherwise tag impl fn as constructor␊ diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index 335738c1..39d0b36c 100644 Binary files a/examples/napi/__test__/typegen.spec.ts.snap and b/examples/napi/__test__/typegen.spec.ts.snap differ diff --git a/examples/napi/__test__/values.spec.ts b/examples/napi/__test__/values.spec.ts index d6ba3956..9f32fce0 100644 --- a/examples/napi/__test__/values.spec.ts +++ b/examples/napi/__test__/values.spec.ts @@ -86,6 +86,7 @@ import { chronoDateToMillis, derefUint8Array, chronoDateAdd1Minute, + bufferPassThrough, } from '../' test('export const', (t) => { @@ -382,6 +383,12 @@ test('async move', async (t) => { t.is(await asyncMultiTwo(2), 4) }) +test('buffer passthrough', async (t) => { + const fixture = Buffer.from('hello world') + const ret = await bufferPassThrough(fixture) + t.deepEqual(ret, fixture) +}) + test('either', (t) => { t.is(eitherStringOrNumber(2), 2) t.is(eitherStringOrNumber('hello'), 'hello'.length) diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index 2a6ae7e7..9a636c1b 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -142,6 +142,7 @@ export function convertU32Array(input: Uint32Array): Array export function createExternalTypedArray(): Uint32Array export function mutateTypedArray(input: Float32Array): void export function derefUint8Array(a: Uint8Array, b: Uint8ClampedArray): number +export function bufferPassThrough(buf: Buffer): Promise /** * `constructor` option for `struct` requires all fields to be public, * otherwise tag impl fn as constructor diff --git a/examples/napi/src/typed_array.rs b/examples/napi/src/typed_array.rs index c4a769db..26082017 100644 --- a/examples/napi/src/typed_array.rs +++ b/examples/napi/src/typed_array.rs @@ -33,3 +33,8 @@ fn mutate_typed_array(mut input: Float32Array) { fn deref_uint8_array(a: Uint8Array, b: Uint8ClampedArray) -> u32 { (a.len() + b.len()) as u32 } + +#[napi] +async fn buffer_pass_through(buf: Buffer) -> Result { + Ok(buf) +}