napi/js_values/string/
latin1.rs

1#[cfg(feature = "napi10")]
2use std::ffi::c_void;
3
4use crate::{bindgen_prelude::ToNapiValue, sys, JsString, Result};
5
6#[cfg(feature = "napi10")]
7use crate::Env;
8
9pub struct JsStringLatin1<'env> {
10  pub(crate) inner: JsString<'env>,
11  pub(crate) buf: &'env [u8],
12  pub(crate) _inner_buf: Vec<u8>,
13}
14
15impl<'env> JsStringLatin1<'env> {
16  #[cfg(feature = "napi10")]
17  /// Try to create a new JavaScript latin1 string from a Rust `Vec<u8>` without copying the data
18  /// ## Behavior
19  ///
20  /// The `copied` parameter in the underlying `node_api_create_external_string_latin1` call
21  /// indicates whether the string data was copied into V8's heap rather than being used
22  /// as an external reference.
23  ///
24  /// ### When `copied` is `true`:
25  /// - String data is copied to V8's heap
26  /// - Finalizer is called immediately if provided
27  /// - Original buffer can be freed after the call
28  /// - Performance benefit of external strings is not achieved
29  ///
30  /// ### When `copied` is `false`:
31  /// - V8 creates an external string that references the original buffer without copying
32  /// - Original buffer must remain valid for the lifetime of the JS string
33  /// - Finalizer called when string is garbage collected
34  /// - Memory usage and copying overhead is reduced
35  ///
36  /// ## Common scenarios where `copied` is `true`:
37  /// - String is too short (typically < 10-15 characters)
38  /// - V8 heap is under memory pressure
39  /// - V8 is running with pointer compression or sandbox features
40  /// - Invalid Latin-1 encoding that requires sanitization
41  /// - Platform doesn't support external strings
42  /// - Memory alignment issues with the provided buffer
43  ///
44  /// The `copied` parameter serves as feedback to understand whether the external string
45  /// optimization was successful or if V8 fell back to traditional string creation.
46  pub fn from_data(env: &'env Env, data: Vec<u8>) -> Result<JsStringLatin1<'env>> {
47    use std::{mem, ptr};
48
49    use crate::{check_status, Error, Status, Value, ValueType};
50
51    if data.is_empty() {
52      return Err(Error::new(
53        Status::InvalidArg,
54        "Cannot create external string from empty data".to_owned(),
55      ));
56    }
57
58    let mut raw_value = ptr::null_mut();
59    let mut copied = false;
60    let data_ptr = data.as_ptr();
61    let len = data.len();
62    let cap = data.capacity();
63    let finalize_hint = Box::into_raw(Box::new((len, cap)));
64
65    check_status!(
66      unsafe {
67        sys::node_api_create_external_string_latin1(
68          env.0,
69          data_ptr.cast(),
70          len as isize,
71          Some(drop_latin1_string),
72          finalize_hint.cast(),
73          &mut raw_value,
74          &mut copied,
75        )
76      },
77      "Failed to create external string latin1"
78    )?;
79
80    let inner_buf = if copied {
81      // If the data was copied, the finalizer won't be called
82      // We need to clean up the finalize_hint and let the Vec be dropped
83      unsafe {
84        let _ = Box::from_raw(finalize_hint);
85      };
86      data
87    } else {
88      // Only forget the data if it wasn't copied
89      // The finalizer will handle cleanup
90      mem::forget(data);
91      vec![]
92    };
93
94    Ok(Self {
95      inner: JsString(
96        Value {
97          env: env.0,
98          value: raw_value,
99          value_type: ValueType::String,
100        },
101        std::marker::PhantomData,
102      ),
103      buf: unsafe { std::slice::from_raw_parts(data_ptr, len) },
104      _inner_buf: inner_buf,
105    })
106  }
107
108  #[cfg(feature = "napi10")]
109  /// Creates an external Latin-1 string from raw data with a custom finalize callback.
110  ///
111  /// ## Safety
112  ///
113  /// The caller must ensure that:
114  /// - The data pointer is valid for the lifetime of the string
115  /// - The finalize callback properly cleans up the data
116  ///
117  /// ## Behavior
118  ///
119  /// The `copied` parameter in the underlying `node_api_create_external_string_latin1` call
120  /// indicates whether the string data was copied into V8's heap rather than being used
121  /// as an external reference.
122  ///
123  /// ### When `copied` is `true`:
124  /// - String data is copied to V8's heap
125  /// - Finalizer is called immediately if provided
126  /// - Original buffer can be freed after the call
127  /// - Performance benefit of external strings is not achieved
128  ///
129  /// ### When `copied` is `false`:
130  /// - V8 creates an external string that references the original buffer without copying
131  /// - Original buffer must remain valid for the lifetime of the JS string
132  /// - Finalizer called when string is garbage collected
133  /// - Memory usage and copying overhead is reduced
134  ///
135  /// ## Common scenarios where `copied` is `true`:
136  /// - String is too short (typically < 10-15 characters)
137  /// - V8 heap is under memory pressure
138  /// - V8 is running with pointer compression or sandbox features
139  /// - Invalid Latin-1 encoding that requires sanitization
140  /// - Platform doesn't support external strings
141  /// - Memory alignment issues with the provided buffer
142  ///
143  /// The `copied` parameter serves as feedback to understand whether the external string
144  /// optimization was successful or if V8 fell back to traditional string creation.
145  pub unsafe fn from_external<T: 'env, F: FnOnce(Env, T) + 'env>(
146    env: &'env Env,
147    data: *const u8,
148    len: usize,
149    finalize_hint: T,
150    finalize_callback: F,
151  ) -> Result<JsStringLatin1<'env>> {
152    use std::ptr;
153
154    use crate::{check_status, Error, Status, Value, ValueType};
155
156    if data.is_null() {
157      return Err(Error::new(
158        Status::InvalidArg,
159        "Data pointer should not be null".to_owned(),
160      ));
161    }
162
163    let hint_ptr = Box::into_raw(Box::new((finalize_hint, finalize_callback)));
164    let mut raw_value = ptr::null_mut();
165    let mut copied = false;
166
167    check_status!(
168      unsafe {
169        sys::node_api_create_external_string_latin1(
170          env.0,
171          data.cast(),
172          len as isize,
173          Some(finalize_with_custom_callback::<T, F>),
174          hint_ptr.cast(),
175          &mut raw_value,
176          &mut copied,
177        )
178      },
179      "Failed to create external string latin1"
180    )?;
181
182    if copied {
183      unsafe {
184        let (hint, finalize) = *Box::from_raw(hint_ptr);
185        finalize(*env, hint);
186      }
187    }
188
189    Ok(Self {
190      inner: JsString(
191        Value {
192          env: env.0,
193          value: raw_value,
194          value_type: ValueType::String,
195        },
196        std::marker::PhantomData,
197      ),
198      buf: unsafe { std::slice::from_raw_parts(data, len) },
199      _inner_buf: vec![],
200    })
201  }
202
203  #[cfg(feature = "napi10")]
204  pub fn from_static(env: &'env Env, string: &'static str) -> Result<JsStringLatin1<'env>> {
205    use std::ptr;
206
207    use crate::{check_status, Error, Status, Value, ValueType};
208
209    if string.is_empty() {
210      return Err(Error::new(
211        Status::InvalidArg,
212        "Data pointer should not be null".to_owned(),
213      ));
214    }
215
216    let mut raw_value = ptr::null_mut();
217    let mut copied = false;
218
219    check_status!(
220      unsafe {
221        sys::node_api_create_external_string_latin1(
222          env.0,
223          string.as_ptr().cast(),
224          string.len() as isize,
225          None,
226          ptr::null_mut(),
227          &mut raw_value,
228          &mut copied,
229        )
230      },
231      "Failed to create external string latin1"
232    )?;
233
234    Ok(Self {
235      inner: JsString(
236        Value {
237          env: env.0,
238          value: raw_value,
239          value_type: ValueType::String,
240        },
241        std::marker::PhantomData,
242      ),
243      buf: string.as_bytes(),
244      _inner_buf: vec![],
245    })
246  }
247
248  pub fn as_slice(&self) -> &[u8] {
249    self.buf
250  }
251
252  pub fn len(&self) -> usize {
253    self.buf.len()
254  }
255
256  pub fn is_empty(&self) -> bool {
257    self.buf.is_empty()
258  }
259
260  pub fn take(self) -> Vec<u8> {
261    self.as_slice().to_vec()
262  }
263
264  pub fn into_value(self) -> JsString<'env> {
265    self.inner
266  }
267
268  #[cfg(feature = "latin1")]
269  pub fn into_latin1_string(self) -> Result<String> {
270    let mut dst_str = unsafe { String::from_utf8_unchecked(vec![0; self.len() * 2 + 1]) };
271    encoding_rs::mem::convert_latin1_to_str(self.buf, dst_str.as_mut_str());
272    Ok(dst_str)
273  }
274}
275
276impl From<JsStringLatin1<'_>> for Vec<u8> {
277  fn from(value: JsStringLatin1) -> Self {
278    value.take()
279  }
280}
281
282impl ToNapiValue for JsStringLatin1<'_> {
283  unsafe fn to_napi_value(_: sys::napi_env, val: JsStringLatin1) -> Result<sys::napi_value> {
284    Ok(val.inner.0.value)
285  }
286}
287
288#[cfg(feature = "napi10")]
289extern "C" fn drop_latin1_string(
290  _: sys::node_api_basic_env,
291  finalize_data: *mut c_void,
292  finalize_hint: *mut c_void,
293) {
294  let (size, capacity): (usize, usize) = unsafe { *Box::from_raw(finalize_hint.cast()) };
295  if size == 0 || finalize_data.is_null() {
296    return;
297  }
298  let data: Vec<u8> = unsafe { Vec::from_raw_parts(finalize_data.cast(), size, capacity) };
299  drop(data);
300}
301
302#[cfg(feature = "napi10")]
303extern "C" fn finalize_with_custom_callback<T, F: FnOnce(Env, T)>(
304  env: sys::node_api_basic_env,
305  _finalize_data: *mut c_void,
306  finalize_hint: *mut c_void,
307) {
308  let (hint, callback) = unsafe { *Box::from_raw(finalize_hint as *mut (T, F)) };
309  callback(Env(env.cast()), hint);
310}