impl To/FromNapiValue for HashMap

This commit is contained in:
forehalo 2021-09-24 17:01:54 +08:00 committed by LongYinan
parent e77d2e95ef
commit f4c0b0639b
16 changed files with 170 additions and 21 deletions

View file

@ -255,20 +255,25 @@ async function processIntermediateTypeFile(source: string, target: string) {
} }
}) })
for (const [name, def] of impls.entries()) { for (const [name, classDef] of classes.entries()) {
const classDef = classes.get(name) const implDef = impls.get(name)
dts += `export class ${name} { dts += `export class ${name} {
${(classDef ?? '') ${classDef
.split('\n') .split('\n')
.map((line) => line.trim()) .map((line) => line.trim())
.join('\n ')} .join('\n ')}`
${def
.split('\n') if (implDef) {
.map((line) => line.trim()) dts +=
.join('\n ')} '\n ' +
} implDef
` .split('\n')
.map((line) => line.trim())
.join('\n ')
}
dts += '\n}\n'
} }
await unlinkAsync(source) await unlinkAsync(source)

View file

@ -35,6 +35,7 @@ pub static TYPE_REGEXES: Lazy<HashMap<&'static str, Regex>> = Lazy::new(|| {
("Vec", Regex::new(r"^Vec < (.*) >$").unwrap()), ("Vec", Regex::new(r"^Vec < (.*) >$").unwrap()),
("Option", Regex::new(r"^Option < (.*) >").unwrap()), ("Option", Regex::new(r"^Option < (.*) >").unwrap()),
("Result", Regex::new(r"^Result < (.*) >").unwrap()), ("Result", Regex::new(r"^Result < (.*) >").unwrap()),
("HashMap", Regex::new(r"HashMap < (.*), (.*) >").unwrap()),
]); ]);
map 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) => { s if s.starts_with("Result") && TYPE_REGEXES["Result"].is_match(s) => {
let captures = TYPE_REGEXES["Result"].captures(s).unwrap(); let captures = TYPE_REGEXES["Result"].captures(s).unwrap();
let inner = captures.get(1).unwrap().as_str(); let inner = captures.get(1).unwrap().as_str();
if omit_top_level_result { if omit_top_level_result {
str_to_ts_type(inner, false) str_to_ts_type(inner, false)
} else { } else {
format!("Error | {}", str_to_ts_type(inner, false)) 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(), s => s.to_owned(),
} }
} }

View file

@ -685,9 +685,10 @@ impl ConvertToAST for syn::ItemStruct {
let (js_name, name) = match &field.ident { let (js_name, name) = match &field.ident {
Some(ident) => ( Some(ident) => (
field_opts field_opts.js_name().map_or_else(
.js_name() || ident.to_string().to_case(Case::Camel),
.map_or_else(|| ident.to_string(), |(js_name, _)| js_name.to_owned()), |(js_name, _)| js_name.to_owned(),
),
syn::Member::Named(ident.clone()), syn::Member::Named(ident.clone()),
), ),
None => { None => {

View file

@ -4,6 +4,7 @@ use std::ptr;
mod array; mod array;
mod boolean; mod boolean;
mod buffer; mod buffer;
mod map;
mod nil; mod nil;
mod number; mod number;
mod object; mod object;

View file

@ -0,0 +1,44 @@
use std::collections::HashMap;
use std::hash::Hash;
use crate::bindgen_prelude::{Env, Result, ToNapiValue, *};
impl<K, V> TypeName for HashMap<K, V> {
fn type_name() -> &'static str {
"HashMap"
}
}
impl<K, V> ToNapiValue for HashMap<K, V>
where
K: AsRef<str>,
V: ToNapiValue,
{
unsafe fn to_napi_value(raw_env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
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<K, V> FromNapiValue for HashMap<K, V>
where
K: From<String> + Eq + Hash,
V: FromNapiValue,
{
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
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)
}
}

View file

@ -19,8 +19,8 @@ impl Object {
Ok(Object { env, inner: ptr }) Ok(Object { env, inner: ptr })
} }
pub fn get<T: FromNapiValue>(&self, field: String) -> Result<Option<T>> { pub fn get<K: AsRef<str>, V: FromNapiValue>(&self, field: K) -> Result<Option<V>> {
let c_field = CString::new(field)?; let c_field = CString::new(field.as_ref())?;
unsafe { unsafe {
let mut ret = ptr::null_mut(); let mut ret = ptr::null_mut();
@ -36,16 +36,16 @@ impl Object {
Ok(if ty == ValueType::Undefined { Ok(if ty == ValueType::Undefined {
None None
} else { } else {
Some(T::from_napi_value(self.env, ret)?) Some(V::from_napi_value(self.env, ret)?)
}) })
} }
} }
pub fn set<T: ToNapiValue>(&mut self, field: String, val: T) -> Result<()> { pub fn set<K: AsRef<str>, V: ToNapiValue>(&mut self, field: K, val: V) -> Result<()> {
let c_field = CString::new(field)?; let c_field = CString::new(field.as_ref())?;
unsafe { unsafe {
let napi_val = T::to_napi_value(self.env, val)?; let napi_val = V::to_napi_value(self.env, val)?;
check_status!( check_status!(
sys::napi_set_named_property(self.env, self.inner, c_field.as_ptr(), napi_val), sys::napi_set_named_property(self.env, self.inner, c_field.as_ptr(), napi_val),

View file

@ -13,7 +13,10 @@ napi3 = ["napi/napi3"]
[dependencies] [dependencies]
napi-derive = { path = "../../crates/macro", features = ["type-def"] } 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] [build-dependencies]
napi-build = { path = "../../crates/build" } napi-build = { path = "../../crates/build" }

View file

@ -13,6 +13,7 @@ Generated by [AVA](https://avajs.dev).
export function sumNums(nums: Array<number>): number␊ export function sumNums(nums: Array<number>): number␊
export function getCwd(callback: (arg0: string) => void): void␊ export function getCwd(callback: (arg0: string) => void): void␊
export function readFile(callback: (arg0: Error | undefined, arg1: string | null) => 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 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 enum CustomNumEnum { One = 1, Two = 2, Three = 3, Four = 4, Six = 6, Eight = 8, Nine = 9, Ten = 10 }␊
export function enumToI32(e: CustomNumEnum): number␊ export function enumToI32(e: CustomNumEnum): number␊
@ -33,4 +34,11 @@ Generated by [AVA](https://avajs.dev).
set name(name: string)␊ set name(name: string)␊
whoami(): string␊ whoami(): string␊
}␊ }␊
export class PackageJson {␊
name: string␊
version: string␊
dependencies: Record<string, string> | null␊
devDependencies: Record<string, string> | null␊
constructor(name: string, version: string, dependencies: Record<string, string> | null, devDependencies: Record<string, string> | null)␊
}␊
` `

View file

@ -20,6 +20,7 @@ import {
mapOption, mapOption,
readFile, readFile,
throwError, throwError,
readPackageJson,
} from '../' } from '../'
test('number', (t) => { test('number', (t) => {
@ -67,6 +68,12 @@ test('class', (t) => {
dog.name = '可乐' dog.name = '可乐'
t.is(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) => { test('callback', (t) => {

View file

@ -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',
]

Binary file not shown.

View file

@ -3,6 +3,7 @@ export function getNums(): Array<number>
export function sumNums(nums: Array<number>): number export function sumNums(nums: Array<number>): number
export function getCwd(callback: (arg0: string) => void): void export function getCwd(callback: (arg0: string) => void): void
export function readFile(callback: (arg0: Error | undefined, arg1: string | null) => 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 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 enum CustomNumEnum { One = 1, Two = 2, Three = 3, Four = 4, Six = 6, Eight = 8, Nine = 9, Ten = 10 }
export function enumToI32(e: CustomNumEnum): number export function enumToI32(e: CustomNumEnum): number
@ -23,3 +24,10 @@ export class Animal {
set name(name: string) set name(name: string)
whoami(): string whoami(): string
} }
export class PackageJson {
name: string
version: string
dependencies: Record<string, string> | null
devDependencies: Record<string, string> | null
constructor(name: string, version: string, dependencies: Record<string, string> | null, devDependencies: Record<string, string> | null)
}

View file

@ -1,3 +1,5 @@
use std::{collections::HashMap, fs};
use napi::bindgen_prelude::*; use napi::bindgen_prelude::*;
use crate::r#enum::Kind; 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<HashMap<String, String>>,
#[serde(rename = "devDependencies")]
pub dev_dependencies: Option<HashMap<String, String>>,
}
#[napi]
fn read_package_json() -> Result<PackageJson> {
let raw = fs::read_to_string("package.json")?;
let p: PackageJson = serde_json::from_str(&raw)?;
Ok(p)
}

View file

@ -1,5 +1,7 @@
#[macro_use] #[macro_use]
extern crate napi_derive; extern crate napi_derive;
#[macro_use]
extern crate serde_derive;
mod array; mod array;
mod callback; mod callback;

View file

@ -8,7 +8,7 @@ fn list_obj_keys(obj: Object) -> Vec<String> {
#[napi] #[napi]
fn create_obj(env: Env) -> Object { fn create_obj(env: Env) -> Object {
let mut obj = env.create_object().unwrap(); let mut obj = env.create_object().unwrap();
obj.set("test".to_owned(), 1).unwrap(); obj.set("test", 1).unwrap();
obj obj
} }