diff --git a/README.md b/README.md index 17b91c81..8c91486f 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ yarn test | [napi_create_bigint_int64](https://nodejs.org/api/n-api.html#n_api_napi_create_bigint_int64) | 6 | v10.7.0 | ✅ | | [napi_create_bigint_uint64](https://nodejs.org/api/n-api.html#n_api_napi_create_bigint_uint64) | 6 | v10.7.0 | ✅ | | [napi_create_bigint_words](https://nodejs.org/api/n-api.html#n_api_napi_create_bigint_words) | 6 | v10.7.0 | ✅ | -| [napi_create_string_latin1](https://nodejs.org/api/n-api.html#n_api_napi_create_string_latin1) | 1 | v8.0.0 | ⛔️ | +| [napi_create_string_latin1](https://nodejs.org/api/n-api.html#n_api_napi_create_string_latin1) | 1 | v8.0.0 | ✅ | | [napi_create_string_utf16](https://nodejs.org/api/n-api.html#n_api_napi_create_string_utf16) | 1 | v8.0.0 | ✅ | | [napi_create_string_utf8](https://nodejs.org/api/n-api.html#n_api_napi_create_string_utf8) | 1 | v8.0.0 | ✅ | @@ -181,12 +181,12 @@ yarn test | [napi_get_value_external](https://nodejs.org/api/n-api.html#n_api_napi_get_value_external) | 1 | v8.0.0 | ✅ | | [napi_get_value_int32](https://nodejs.org/api/n-api.html#n_api_napi_get_value_int32) | 1 | v8.0.0 | ✅ | | [napi_get_value_int64](https://nodejs.org/api/n-api.html#n_api_napi_get_value_int64) | 1 | v8.0.0 | ✅ | -| [napi_get_value_string_latin1](https://nodejs.org/api/n-api.html#n_api_napi_get_value_string_latin1) | 1 | v8.0.0 | ⛔️ | +| [napi_get_value_string_latin1](https://nodejs.org/api/n-api.html#n_api_napi_get_value_string_latin1) | 1 | v8.0.0 | ✅ | | [napi_get_value_string_utf8](https://nodejs.org/api/n-api.html#n_api_napi_get_value_string_utf8) | 1 | v8.0.0 | ✅ | | [napi_get_value_string_utf16](https://nodejs.org/api/n-api.html#n_api_napi_get_value_string_utf16) | 1 | v8.0.0 | ✅ | | [napi_get_value_uint32](https://nodejs.org/api/n-api.html#n_api_napi_get_value_uint32) | 1 | v8.0.0 | ✅ | | [napi_get_boolean](https://nodejs.org/api/n-api.html#n_api_napi_get_boolean) | 1 | v8.0.0 | ✅ | -| [napi_get_global](https://nodejs.org/api/n-api.html#n_api_napi_get_global) | 1 | v8.0.0 | ⛔️ | +| [napi_get_global](https://nodejs.org/api/n-api.html#n_api_napi_get_global) | 1 | v8.0.0 | ✅ | | [napi_get_null](https://nodejs.org/api/n-api.html#n_api_napi_get_null) | 1 | v8.0.0 | ✅ | | [napi_get_undefined](https://nodejs.org/api/n-api.html#n_api_napi_get_undefined) | 1 | v8.0.0 | ✅ | diff --git a/napi/Cargo.toml b/napi/Cargo.toml index 34a6da47..55c6d1d2 100644 --- a/napi/Cargo.toml +++ b/napi/Cargo.toml @@ -13,10 +13,15 @@ edition = "2018" libuv = ["futures"] tokio_rt = ["futures", "tokio", "once_cell"] serde-json = ["serde", "serde_json"] +latin1 = ["encoding_rs"] [dependencies] napi-sys = { version = "0.4", path = "../sys" } +[dependencies.encoding_rs] +version = "0.8" +optional = true + [dependencies.futures] version = "0.3" optional = true diff --git a/napi/src/env.rs b/napi/src/env.rs index 286a9804..5dde9187 100644 --- a/napi/src/env.rs +++ b/napi/src/env.rs @@ -196,6 +196,20 @@ impl Env { Ok(JsString::from_raw_unchecked(self.0, raw_value)) } + #[inline] + pub fn create_string_latin1(&self, chars: &[u8]) -> Result { + let mut raw_value = ptr::null_mut(); + check_status(unsafe { + sys::napi_create_string_latin1( + self.0, + chars.as_ptr() as *const _, + chars.len() as u64, + &mut raw_value, + ) + })?; + Ok(JsString::from_raw_unchecked(self.0, raw_value)) + } + #[inline] pub fn create_symbol_from_js_string(&self, description: JsString) -> Result { let mut result = ptr::null_mut(); diff --git a/napi/src/js_values/string.rs b/napi/src/js_values/string.rs index d9ee38df..6190bd78 100644 --- a/napi/src/js_values/string.rs +++ b/napi/src/js_values/string.rs @@ -4,6 +4,9 @@ use std::ptr; use std::slice; use std::str; +#[cfg(feature = "latin1")] +use encoding_rs; + use super::Value; use crate::error::check_status; use crate::{sys, Error, Result, Status}; @@ -13,20 +16,29 @@ pub struct JsString(pub(crate) Value); impl JsString { #[inline] - pub fn len(&self) -> Result { + pub fn utf8_len(&self) -> Result { let mut length = 0; check_status(unsafe { sys::napi_get_value_string_utf8(self.0.env, self.0.value, ptr::null_mut(), 0, &mut length) })?; Ok(length as usize) } + + #[inline] + pub fn latin1_len(&self) -> Result { + let mut length = 0; + check_status(unsafe { + sys::napi_get_value_string_latin1(self.0.env, self.0.value, ptr::null_mut(), 0, &mut length) + })?; + Ok(length as usize) + } } impl JsString { #[inline] - pub fn get_ref(&self) -> Result<&[u8]> { + pub fn get_utf8(&self) -> Result<&[u8]> { let mut written_char_count: u64 = 0; - let len = self.len()? + 1; + let len = self.utf8_len()? + 1; let mut result = Vec::with_capacity(len); unsafe { check_status(sys::napi_get_value_string_utf8( @@ -46,9 +58,31 @@ impl JsString { } #[inline] - pub fn chars(&self) -> Result<&[char]> { + pub fn get_latin1(&self) -> Result<(&[u8], usize)> { let mut written_char_count: u64 = 0; - let len = self.len()? + 1; + let len = self.latin1_len()? + 1; + let mut result = Vec::with_capacity(len); + unsafe { + check_status(sys::napi_get_value_string_latin1( + self.0.env, + self.0.value, + result.as_mut_ptr(), + len as u64, + &mut written_char_count, + ))?; + let ptr = result.as_ptr(); + mem::forget(result); + Ok(( + slice::from_raw_parts(ptr as *const u8, written_char_count as usize), + written_char_count as usize, + )) + } + } + + #[inline] + pub fn get_utf8_chars(&self) -> Result<&[char]> { + let mut written_char_count: u64 = 0; + let len = self.utf8_len()? + 1; let mut result = Vec::with_capacity(len); unsafe { check_status(sys::napi_get_value_string_utf8( @@ -69,13 +103,21 @@ impl JsString { } pub fn as_str(&self) -> Result<&str> { - str::from_utf8(self.get_ref()?) + str::from_utf8(self.get_utf8()?) .map_err(|e| Error::new(Status::GenericFailure, format!("{:?}", e))) } + #[cfg(feature = "latin1")] + pub fn as_latin1_string(&self) -> Result { + let (latin1_bytes, len) = self.get_latin1()?; + let mut dst_str = unsafe { String::from_utf8_unchecked(vec![0; len * 2 + 1]) }; + encoding_rs::mem::convert_latin1_to_str(latin1_bytes, dst_str.as_mut_str()); + Ok(dst_str) + } + pub fn get_ref_mut(&mut self) -> Result<&mut [u8]> { let mut written_char_count: u64 = 0; - let len = self.len()? + 1; + let len = self.utf8_len()? + 1; let mut result = Vec::with_capacity(len); unsafe { check_status(sys::napi_get_value_string_utf8( @@ -100,7 +142,7 @@ impl TryFrom for Vec { type Error = Error; fn try_from(value: JsString) -> Result> { - let mut result = Vec::with_capacity(value.len()? + 1); // Leave room for trailing null byte + let mut result = Vec::with_capacity(value.utf8_len()? + 1); // Leave room for trailing null byte unsafe { let mut written_char_count = 0; diff --git a/test_module/Cargo.toml b/test_module/Cargo.toml index 8e02d109..13486db3 100644 --- a/test_module/Cargo.toml +++ b/test_module/Cargo.toml @@ -9,7 +9,7 @@ crate-type = ["cdylib"] [dependencies] futures = "0.3" -napi = { path = "../napi", features = ["libuv", "tokio_rt", "serde-json"] } +napi = { path = "../napi", features = ["libuv", "tokio_rt", "serde-json", "latin1"] } napi-derive = { path = "../napi-derive" } serde = "1" serde_bytes = "0.11" diff --git a/test_module/__test__/string.spec.js.md b/test_module/__test__/string.spec.js.md deleted file mode 100644 index 7e3709c6..00000000 --- a/test_module/__test__/string.spec.js.md +++ /dev/null @@ -1,11 +0,0 @@ -# Snapshot report for `test_module/__test__/string.spec.js` - -The actual snapshot is saved in `string.spec.js.snap`. - -Generated by [AVA](https://avajs.dev). - -## should be able to concat string - -> Snapshot 1 - - 'JavaScript 🌳 你好 napi + Rust 🦀 string!' diff --git a/test_module/__test__/string.spec.js.snap b/test_module/__test__/string.spec.js.snap deleted file mode 100644 index 624728bf..00000000 Binary files a/test_module/__test__/string.spec.js.snap and /dev/null differ diff --git a/test_module/__test__/string.spec.ts b/test_module/__test__/string.spec.ts index 9af2c008..a0898cb5 100644 --- a/test_module/__test__/string.spec.ts +++ b/test_module/__test__/string.spec.ts @@ -6,3 +6,12 @@ test('should be able to concat string', (t) => { const fixture = 'JavaScript 🌳 你好 napi' t.snapshot(bindings.concatString(fixture)) }) + +test('should be able to concat latin1 string', (t) => { + const fixture = '涽¾DEL' + t.snapshot(bindings.concatLatin1String(fixture)) +}) + +test('should be able to crate latin1 string', (t) => { + t.snapshot(bindings.createLatin1()) +}) diff --git a/test_module/__test__/string.spec.ts.md b/test_module/__test__/string.spec.ts.md index ecb76314..f02b4e92 100644 Binary files a/test_module/__test__/string.spec.ts.md and b/test_module/__test__/string.spec.ts.md differ diff --git a/test_module/__test__/string.spec.ts.snap b/test_module/__test__/string.spec.ts.snap index 624728bf..9e347da0 100644 Binary files a/test_module/__test__/string.spec.ts.snap and b/test_module/__test__/string.spec.ts.snap differ diff --git a/test_module/src/string.rs b/test_module/src/string.rs index 125f5c3f..72197584 100644 --- a/test_module/src/string.rs +++ b/test_module/src/string.rs @@ -1,13 +1,28 @@ use napi::{CallContext, JsString, Module, Result}; #[js_function(1)] -pub fn concat_string(ctx: CallContext) -> Result { +fn concat_string(ctx: CallContext) -> Result { let in_string = ctx.get::(0)?; let out_string = format!("{} + Rust 🦀 string!", in_string.as_str()?); ctx.env.create_string_from_std(out_string) } +#[js_function(1)] +fn concat_latin1_string(ctx: CallContext) -> Result { + let in_string = ctx.get::(0)?; + let out_string = format!("{} + Rust 🦀 string!", in_string.as_latin1_string()?); + ctx.env.create_string_from_std(out_string) +} + +#[js_function] +fn create_latin1(ctx: CallContext) -> Result { + let bytes = vec![169, 191]; + ctx.env.create_string_latin1(bytes.as_slice()) +} + pub fn register_js(module: &mut Module) -> Result<()> { module.create_named_method("concatString", concat_string)?; + module.create_named_method("concatLatin1String", concat_latin1_string)?; + module.create_named_method("createLatin1", create_latin1)?; Ok(()) }