From 371cc5eab0afc8a2cef9b8af0503f80435b7c571 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Thu, 3 Sep 2020 19:11:30 +0800 Subject: [PATCH] feat(napi): string latin1 support --- README.md | 6 +-- napi/Cargo.toml | 5 ++ napi/src/env.rs | 14 ++++++ napi/src/js_values/string.rs | 58 +++++++++++++++++++---- test_module/Cargo.toml | 2 +- test_module/__test__/string.spec.js.md | 11 ----- test_module/__test__/string.spec.js.snap | Bin 140 -> 0 bytes test_module/__test__/string.spec.ts | 9 ++++ test_module/__test__/string.spec.ts.md | Bin 261 -> 429 bytes test_module/__test__/string.spec.ts.snap | Bin 140 -> 207 bytes test_module/src/string.rs | 17 ++++++- 11 files changed, 98 insertions(+), 24 deletions(-) delete mode 100644 test_module/__test__/string.spec.js.md delete mode 100644 test_module/__test__/string.spec.js.snap 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 624728bff366ce5dd2461b5eacff9e8648fc8738..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 140 zcmZ<^b5sb4$#okJaxu9oQ0v*EAxIV& { 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 ecb763145d6b3921877c4195f3e9a5c2ceebc3ee..f02b4e9245385697fa7fd9586b22d9f1e7eaaeef 100644 GIT binary patch delta 83 zcmZo=TFX3PCtFTpNoJnm#2qsHhnF4Nc4+UReJ-v(3_vjPz1YM#nMz#B$_m99`K382 g3Q4I7iAg!B3MKgp$wi4JsS0qtY=>4J+ON(909cP81ONa4 delta 10 RcmZ3>+{!dz=VVz%9{?2#1AYJi diff --git a/test_module/__test__/string.spec.ts.snap b/test_module/__test__/string.spec.ts.snap index 624728bff366ce5dd2461b5eacff9e8648fc8738..9e347da02288394dff6abfb5709cac2cd83fd159 100644 GIT binary patch literal 207 zcmV;=05JbSRzVr000000009E zVPIfjX3(~HqjqD3=)0dDlV_ZN(W=hK00Oo^T=YFz_svXiv!K44PgVMN=P`ms^FUZS zY3Z6=X{BGgrdLi#SYN;h7Ht5V#}3xd$jTtdsPC0nmKdB|lvz-s@L_(>W`!qv7d&0M zS0OL4AX7nGA*i$%B)+Ucp}3?dGcR2cMYHnZWrwyM+Iwi9i>nVvke~*ZLn{yM2LNV; JG1k-o005^YR*3)r literal 140 zcmZ<^b5sb4$#okJaxu9oQ0v*EAxIV& 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(()) }