From f4c0b0639b80a5c0a2203323d745b8c3f2759684 Mon Sep 17 00:00:00 2001 From: forehalo Date: Fri, 24 Sep 2021 17:01:54 +0800 Subject: [PATCH] impl To/FromNapiValue for HashMap --- cli/src/build.ts | 25 ++++++---- crates/backend/src/typegen.rs | 13 ++++++ crates/macro/src/parser/mod.rs | 7 +-- crates/napi/src/bindgen_runtime/js_values.rs | 1 + .../napi/src/bindgen_runtime/js_values/map.rs | 44 ++++++++++++++++++ .../src/bindgen_runtime/js_values/object.rs | 12 ++--- examples/napi/Cargo.toml | 5 +- examples/napi/__test__/typegen.spec.ts.md | 8 ++++ examples/napi/__test__/typegen.spec.ts.snap | Bin 582 -> 658 bytes examples/napi/__test__/values.spec.ts | 7 +++ examples/napi/__test__/values.spec.ts.md | 38 +++++++++++++++ examples/napi/__test__/values.spec.ts.snap | Bin 0 -> 484 bytes examples/napi/index.d.ts | 8 ++++ examples/napi/src/class.rs | 19 ++++++++ examples/napi/src/lib.rs | 2 + examples/napi/src/object.rs | 2 +- 16 files changed, 170 insertions(+), 21 deletions(-) create mode 100644 crates/napi/src/bindgen_runtime/js_values/map.rs create mode 100644 examples/napi/__test__/values.spec.ts.md create mode 100644 examples/napi/__test__/values.spec.ts.snap diff --git a/cli/src/build.ts b/cli/src/build.ts index 0638968b..e1732f40 100644 --- a/cli/src/build.ts +++ b/cli/src/build.ts @@ -255,20 +255,25 @@ async function processIntermediateTypeFile(source: string, target: string) { } }) - for (const [name, def] of impls.entries()) { - const classDef = classes.get(name) + for (const [name, classDef] of classes.entries()) { + const implDef = impls.get(name) dts += `export class ${name} { - ${(classDef ?? '') + ${classDef .split('\n') .map((line) => line.trim()) - .join('\n ')} - ${def - .split('\n') - .map((line) => line.trim()) - .join('\n ')} -} -` + .join('\n ')}` + + if (implDef) { + dts += + '\n ' + + implDef + .split('\n') + .map((line) => line.trim()) + .join('\n ') + } + + dts += '\n}\n' } await unlinkAsync(source) diff --git a/crates/backend/src/typegen.rs b/crates/backend/src/typegen.rs index 2ee8b960..7dd173f0 100644 --- a/crates/backend/src/typegen.rs +++ b/crates/backend/src/typegen.rs @@ -35,6 +35,7 @@ pub static TYPE_REGEXES: Lazy> = Lazy::new(|| { ("Vec", Regex::new(r"^Vec < (.*) >$").unwrap()), ("Option", Regex::new(r"^Option < (.*) >").unwrap()), ("Result", Regex::new(r"^Result < (.*) >").unwrap()), + ("HashMap", Regex::new(r"HashMap < (.*), (.*) >").unwrap()), ]); map @@ -88,12 +89,24 @@ pub fn str_to_ts_type(ty: &str, omit_top_level_result: bool) -> String { s if s.starts_with("Result") && TYPE_REGEXES["Result"].is_match(s) => { let captures = TYPE_REGEXES["Result"].captures(s).unwrap(); let inner = captures.get(1).unwrap().as_str(); + if omit_top_level_result { str_to_ts_type(inner, false) } else { format!("Error | {}", str_to_ts_type(inner, false)) } } + s if TYPE_REGEXES["HashMap"].is_match(s) => { + let captures = TYPE_REGEXES["HashMap"].captures(s).unwrap(); + let key = captures.get(1).unwrap().as_str(); + let val = captures.get(2).unwrap().as_str(); + + format!( + "Record<{}, {}>", + str_to_ts_type(key, false), + str_to_ts_type(val, false) + ) + } s => s.to_owned(), } } diff --git a/crates/macro/src/parser/mod.rs b/crates/macro/src/parser/mod.rs index e12fee88..712a5a29 100644 --- a/crates/macro/src/parser/mod.rs +++ b/crates/macro/src/parser/mod.rs @@ -685,9 +685,10 @@ impl ConvertToAST for syn::ItemStruct { let (js_name, name) = match &field.ident { Some(ident) => ( - field_opts - .js_name() - .map_or_else(|| ident.to_string(), |(js_name, _)| js_name.to_owned()), + field_opts.js_name().map_or_else( + || ident.to_string().to_case(Case::Camel), + |(js_name, _)| js_name.to_owned(), + ), syn::Member::Named(ident.clone()), ), None => { diff --git a/crates/napi/src/bindgen_runtime/js_values.rs b/crates/napi/src/bindgen_runtime/js_values.rs index 8946ad54..ee7479db 100644 --- a/crates/napi/src/bindgen_runtime/js_values.rs +++ b/crates/napi/src/bindgen_runtime/js_values.rs @@ -4,6 +4,7 @@ use std::ptr; mod array; mod boolean; mod buffer; +mod map; mod nil; mod number; mod object; diff --git a/crates/napi/src/bindgen_runtime/js_values/map.rs b/crates/napi/src/bindgen_runtime/js_values/map.rs new file mode 100644 index 00000000..ddbd33e0 --- /dev/null +++ b/crates/napi/src/bindgen_runtime/js_values/map.rs @@ -0,0 +1,44 @@ +use std::collections::HashMap; +use std::hash::Hash; + +use crate::bindgen_prelude::{Env, Result, ToNapiValue, *}; + +impl TypeName for HashMap { + fn type_name() -> &'static str { + "HashMap" + } +} + +impl ToNapiValue for HashMap +where + K: AsRef, + V: ToNapiValue, +{ + unsafe fn to_napi_value(raw_env: sys::napi_env, val: Self) -> Result { + let env = Env::from(raw_env); + let mut obj = env.create_object()?; + for (k, v) in val.into_iter() { + obj.set(k.as_ref(), v)?; + } + + Object::to_napi_value(raw_env, obj) + } +} + +impl FromNapiValue for HashMap +where + K: From + Eq + Hash, + V: FromNapiValue, +{ + unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result { + let obj = Object::from_napi_value(env, napi_val)?; + let mut map = HashMap::new(); + for key in Object::keys(&obj)?.into_iter() { + if let Some(val) = obj.get(&key)? { + map.insert(K::from(key), val); + } + } + + Ok(map) + } +} diff --git a/crates/napi/src/bindgen_runtime/js_values/object.rs b/crates/napi/src/bindgen_runtime/js_values/object.rs index 768ce591..90f62fb6 100644 --- a/crates/napi/src/bindgen_runtime/js_values/object.rs +++ b/crates/napi/src/bindgen_runtime/js_values/object.rs @@ -19,8 +19,8 @@ impl Object { Ok(Object { env, inner: ptr }) } - pub fn get(&self, field: String) -> Result> { - let c_field = CString::new(field)?; + pub fn get, V: FromNapiValue>(&self, field: K) -> Result> { + let c_field = CString::new(field.as_ref())?; unsafe { let mut ret = ptr::null_mut(); @@ -36,16 +36,16 @@ impl Object { Ok(if ty == ValueType::Undefined { None } else { - Some(T::from_napi_value(self.env, ret)?) + Some(V::from_napi_value(self.env, ret)?) }) } } - pub fn set(&mut self, field: String, val: T) -> Result<()> { - let c_field = CString::new(field)?; + pub fn set, V: ToNapiValue>(&mut self, field: K, val: V) -> Result<()> { + let c_field = CString::new(field.as_ref())?; unsafe { - let napi_val = T::to_napi_value(self.env, val)?; + let napi_val = V::to_napi_value(self.env, val)?; check_status!( sys::napi_set_named_property(self.env, self.inner, c_field.as_ptr(), napi_val), diff --git a/examples/napi/Cargo.toml b/examples/napi/Cargo.toml index 3911e133..f864ae91 100644 --- a/examples/napi/Cargo.toml +++ b/examples/napi/Cargo.toml @@ -13,7 +13,10 @@ napi3 = ["napi/napi3"] [dependencies] napi-derive = { path = "../../crates/macro", features = ["type-def"] } -napi = { path = "../../crates/napi", features = ["latin1"] } +napi = { path = "../../crates/napi", features = ["latin1", "serde-json"] } +serde = "1" +serde_derive = "1" +serde_json = "1" [build-dependencies] napi-build = { path = "../../crates/build" } \ No newline at end of file diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index 8981df32..9cf5bdc4 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -13,6 +13,7 @@ Generated by [AVA](https://avajs.dev). export function sumNums(nums: Array): number␊ export function getCwd(callback: (arg0: string) => void): void␊ export function readFile(callback: (arg0: Error | undefined, arg1: string | null) => void): void␊ + export function readPackageJson(): PackageJson␊ export enum Kind { Dog = 0, Cat = 1, Duck = 2 }␊ export enum CustomNumEnum { One = 1, Two = 2, Three = 3, Four = 4, Six = 6, Eight = 8, Nine = 9, Ten = 10 }␊ export function enumToI32(e: CustomNumEnum): number␊ @@ -33,4 +34,11 @@ Generated by [AVA](https://avajs.dev). set name(name: string)␊ whoami(): string␊ }␊ + export class PackageJson {␊ + name: string␊ + version: string␊ + dependencies: Record | null␊ + devDependencies: Record | null␊ + constructor(name: string, version: string, dependencies: Record | null, devDependencies: Record | null)␊ + }␊ ` diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index 095ccffd346823a0aa998ea32b7a9bffb7ffca4e..bc94ec3ffba09ae847bfdf012e57fa607ea6d597 100644 GIT binary patch literal 658 zcmV;D0&V?4RzV>Kg{N&G%uU|e@0{{H3 z2Ek5nKgX47@ZjFhs<<9%>rhprb!ZYruEJ^+=z$^62`r?Jv%2%PZEa(DH2WL{nFol-x!x7Xe zG?_!tpwPiV{>$ERf+pnh3QDVlw7;8^QFfhW~_9>j|%piJ1p)0Qu98hS> z{=i3gO5t4Q+Gi*c$~&nqV$F}@gI?(8`+G!$%fYv|#|?}g=cOduDwMKeXXp_bv7~TR zly_ylCT4L=SV5u?6>xh-JsD}mIG04-PEMqCo#^sROf1pSWeE5Z+}%3!9NX>$D*dTv znsts=j!9*S#ny093>F24b0*w;ghPn5P6Sh%JaFc^&X6>7-I8Diw(#G$UgLn^4a-5d z(3DnNgfE$ss^OJ0)ZHA8C(K$nQ8Hx-Om`u_0$L>#xWWMs^D##&`b1sE9gfCggMwn| zycRX&VJUJOSp)*Gg>jWV9%z=z68B~~tg>o)*H5Gc7Oz0gGsbQJ^0>)3MgAfJ)0B_DY^Z)<= literal 582 zcmV-M0=fM`RzVF00000000Ap zQ$24JK@dHYAS8EGv{SC7E8+wKoJgd|aU>KJ7Y;&{dwXkpW>*wu8Mo){`kh+=XbkdsyM6Ri<3RUx3TW^SImJwQ!Vn{1k zzoUUjwW5p(q8=toVO=l1I_EP>bb6HlzBqFanwX(c97kn1b*b6pXyvF-mRS5Xj8{&H z!J%=uTX-^oRBOqp+E#&4*LQ~G-t|`mC8*)QcZ2pJ!7EdMex+Gi)d=5FCsaEqOVr=q zj!9}QoG6h~3G*ftmqsf&gE1OBDQu2Wtcg0$99m { @@ -67,6 +68,12 @@ test('class', (t) => { dog.name = '可乐' t.is(dog.name, '可乐') + + const packageJson = readPackageJson() + t.is(packageJson.name, 'napi-rs') + t.is(packageJson.version, '0.0.0') + t.is(packageJson.dependencies, null) + t.snapshot(Object.keys(packageJson.devDependencies!).sort()) }) test('callback', (t) => { diff --git a/examples/napi/__test__/values.spec.ts.md b/examples/napi/__test__/values.spec.ts.md new file mode 100644 index 00000000..a33cc770 --- /dev/null +++ b/examples/napi/__test__/values.spec.ts.md @@ -0,0 +1,38 @@ +# Snapshot report for `examples/napi/__test__/values.spec.ts` + +The actual snapshot is saved in `values.spec.ts.snap`. + +Generated by [AVA](https://avajs.dev). + +## class + +> Snapshot 1 + + [ + '@types/debug', + '@types/lodash-es', + '@types/node', + '@types/sinon', + '@typescript-eslint/eslint-plugin', + '@typescript-eslint/parser', + 'ava', + 'benny', + 'c8', + 'cross-env', + 'esbuild', + 'eslint', + 'eslint-config-prettier', + 'eslint-plugin-import', + 'eslint-plugin-prettier', + 'husky', + 'lerna', + 'lint-staged', + 'npm-run-all', + 'prettier', + 'shx', + 'sinon', + 'source-map-support', + 'ts-node', + 'tslib', + 'typescript', + ] diff --git a/examples/napi/__test__/values.spec.ts.snap b/examples/napi/__test__/values.spec.ts.snap new file mode 100644 index 0000000000000000000000000000000000000000..3004ebd58e0d94f103f475c59dc48d22c1d56772 GIT binary patch literal 484 zcmVesFcgLDhCV2TJ}7S?(H*NRBV}PhVrNTIlSZVDv6G6f3~V4KB!({CnUENOk%2#e znE3$=bYtVv=9;!5(x+JG9AEp|-Xerh(jR;tUOjI=9n|{0m;A#fCBNULMTkYR!!ga# zjcfSd0QbNfFaSyr-2>XdIdBWy0k6OZ@D0>bgzN($Z~^pyN8la!0y419CeQ$mfKy}l$aR%3BPbpCBC3ymOa`&%n;B`GvG&glX)DbYMl%?- z?2e6M7R9u~h3KN3LpkN{pf-!ztgAw;85bS2@@Qqbc49whqA`a?X0#>L3#czAUAdt+ z_B|#Qk0SqX&-0k8PIS-wwhWc2wlG~SNljNPnr2(EJ~7Rfj2v(!%+Zw1Jq~N?$o9Bt ztYS<*YX1>ZG8Ey0{{T}=;5*e literal 0 HcmV?d00001 diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index aae4452d..e354a68c 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -3,6 +3,7 @@ export function getNums(): Array export function sumNums(nums: Array): number export function getCwd(callback: (arg0: string) => void): void export function readFile(callback: (arg0: Error | undefined, arg1: string | null) => void): void +export function readPackageJson(): PackageJson export enum Kind { Dog = 0, Cat = 1, Duck = 2 } export enum CustomNumEnum { One = 1, Two = 2, Three = 3, Four = 4, Six = 6, Eight = 8, Nine = 9, Ten = 10 } export function enumToI32(e: CustomNumEnum): number @@ -23,3 +24,10 @@ export class Animal { set name(name: string) whoami(): string } +export class PackageJson { + name: string + version: string + dependencies: Record | null + devDependencies: Record | null + constructor(name: string, version: string, dependencies: Record | null, devDependencies: Record | null) +} diff --git a/examples/napi/src/class.rs b/examples/napi/src/class.rs index a6146b63..91c060ba 100644 --- a/examples/napi/src/class.rs +++ b/examples/napi/src/class.rs @@ -1,3 +1,5 @@ +use std::{collections::HashMap, fs}; + use napi::bindgen_prelude::*; use crate::r#enum::Kind; @@ -40,3 +42,20 @@ impl Animal { } } } + +#[napi(constructor)] +#[derive(Serialize, Deserialize, Debug)] +struct PackageJson { + pub name: String, + pub version: String, + pub dependencies: Option>, + #[serde(rename = "devDependencies")] + pub dev_dependencies: Option>, +} + +#[napi] +fn read_package_json() -> Result { + let raw = fs::read_to_string("package.json")?; + let p: PackageJson = serde_json::from_str(&raw)?; + Ok(p) +} diff --git a/examples/napi/src/lib.rs b/examples/napi/src/lib.rs index b688315e..f1789ff7 100644 --- a/examples/napi/src/lib.rs +++ b/examples/napi/src/lib.rs @@ -1,5 +1,7 @@ #[macro_use] extern crate napi_derive; +#[macro_use] +extern crate serde_derive; mod array; mod callback; diff --git a/examples/napi/src/object.rs b/examples/napi/src/object.rs index 4f4cac2b..690addea 100644 --- a/examples/napi/src/object.rs +++ b/examples/napi/src/object.rs @@ -8,7 +8,7 @@ fn list_obj_keys(obj: Object) -> Vec { #[napi] fn create_obj(env: Env) -> Object { let mut obj = env.create_object().unwrap(); - obj.set("test".to_owned(), 1).unwrap(); + obj.set("test", 1).unwrap(); obj }