pub(super) fn js_bytes_def() -> &'static str {
r#"
/// Wrapper for byte arrays that implements custom FromNapiValue to accept Buffer.from(...).
///
/// NAPI v3's default FromNapiValue for `Vec<u8>` expects `Array<number>`, not Buffer.
/// This wrapper provides custom deserialization that accepts Buffer, Uint8Array, or Array,
/// converting them to `Vec<u8>`. Implements Clone and serde traits for use in struct fields.
#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct JsBytes(pub Vec<u8>);
impl From<Vec<u8>> for JsBytes {
fn from(v: Vec<u8>) -> Self {
JsBytes(v)
}
}
impl From<JsBytes> for Vec<u8> {
fn from(js_bytes: JsBytes) -> Self {
js_bytes.0
}
}
impl AsRef<[u8]> for JsBytes {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl std::ops::Deref for JsBytes {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for JsBytes {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl napi::bindgen_prelude::FromNapiValue for JsBytes {
unsafe fn from_napi_value(env: napi::sys::napi_env, napi_val: napi::sys::napi_value) -> napi::Result<Self> {
use napi::bindgen_prelude::FromNapiValue;
// Try Buffer first (most common for binary data in JS)
if let Ok(buffer) = unsafe { napi::bindgen_prelude::Buffer::from_napi_value(env, napi_val) } {
return Ok(JsBytes(buffer.as_ref().to_vec()));
}
// Try Uint8Array
if let Ok(ua) = unsafe { napi::bindgen_prelude::Uint8Array::from_napi_value(env, napi_val) } {
return Ok(JsBytes(ua.to_vec()));
}
// Fall back to Array[number]
if let Ok(vec) = unsafe { Vec::<u8>::from_napi_value(env, napi_val) } {
return Ok(JsBytes(vec));
}
Err(napi::Error::new(
napi::Status::InvalidArg,
"Expected Buffer, Uint8Array, or Array<number> for bytes field",
))
}
}
impl napi::bindgen_prelude::ToNapiValue for JsBytes {
unsafe fn to_napi_value(env: napi::sys::napi_env, val: Self) -> napi::Result<napi::sys::napi_value> {
// Delegate to Vec<u8>'s implementation (which returns an Uint8Array/Buffer).
unsafe { <Vec<u8> as napi::bindgen_prelude::ToNapiValue>::to_napi_value(env, val.0) }
}
}
"#
}
pub(super) fn js_visitor_ref_def() -> &'static str {
r#"
/// Wrapper for trait visitor types (napi::Object<'static>) that implements Clone.
///
/// Object is not Clone. This wrapper uses Arc<Object<'static>> internally for cheap cloning.
/// The .inner field is public for compatibility with generated code that needs to access
/// the underlying Object for trait dispatch.
pub struct JsVisitorRef {
pub inner: std::sync::Arc<napi::bindgen_prelude::Object<'static>>,
}
impl Clone for JsVisitorRef {
fn clone(&self) -> Self {
JsVisitorRef {
inner: std::sync::Arc::clone(&self.inner),
}
}
}
#[allow(clippy::arc_with_non_send_sync)]
impl From<napi::bindgen_prelude::Object<'static>> for JsVisitorRef {
fn from(visitor: napi::bindgen_prelude::Object<'static>) -> Self {
JsVisitorRef {
inner: std::sync::Arc::new(visitor),
}
}
}
impl From<JsVisitorRef> for napi::bindgen_prelude::Object<'static> {
fn from(visitor_ref: JsVisitorRef) -> Self {
// Object<'static> is Copy (it just holds an env+handle pair), so deref directly.
*visitor_ref.inner
}
}
"#
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_js_bytes_def_has_backtick_wrapped_vec_types() {
let content = js_bytes_def();
assert!(
content.contains("`Vec<u8>`"),
"js_bytes_def should contain backtick-wrapped `Vec<u8>` to prevent rustdoc unclosed-tag warnings"
);
let lines: Vec<&str> = content.lines().collect();
for (idx, line) in lines.iter().enumerate() {
if line.trim_start().starts_with("///") && !line.contains("`Vec<u8>`") {
assert!(
!line.contains("Vec<u8>"),
"Line {} should not contain unwrapped 'Vec<u8>' in doc comments: {}",
idx + 1,
line
);
}
}
}
}