feat: impl FromNapiValue
for serde_json::Number
, fix it for Null
, throw for impossible types (#1052)
fix #1013 Co-authored-by: zeroslope <jsx55242@foxmail.com>
This commit is contained in:
parent
c001038852
commit
ed12bd76bd
6 changed files with 117 additions and 22 deletions
|
@ -1,4 +1,4 @@
|
||||||
use serde_json::{Map, Value};
|
use serde_json::{Map, Number, Value};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
bindgen_runtime::Null, check_status, sys, type_of, Error, JsObject, Result, Status, ValueType,
|
bindgen_runtime::Null, check_status, sys, type_of, Error, JsObject, Result, Status, ValueType,
|
||||||
|
@ -11,20 +11,7 @@ impl ToNapiValue for Value {
|
||||||
match val {
|
match val {
|
||||||
Value::Null => unsafe { Null::to_napi_value(env, Null) },
|
Value::Null => unsafe { Null::to_napi_value(env, Null) },
|
||||||
Value::Bool(b) => unsafe { bool::to_napi_value(env, b) },
|
Value::Bool(b) => unsafe { bool::to_napi_value(env, b) },
|
||||||
Value::Number(n) => {
|
Value::Number(n) => unsafe { Number::to_napi_value(env, n) },
|
||||||
if n.is_i64() {
|
|
||||||
unsafe { i64::to_napi_value(env, n.as_i64().unwrap()) }
|
|
||||||
} else if n.is_f64() {
|
|
||||||
unsafe { f64::to_napi_value(env, n.as_f64().unwrap()) }
|
|
||||||
} else {
|
|
||||||
let n = n.as_u64().unwrap();
|
|
||||||
if n > u32::MAX as u64 {
|
|
||||||
todo!("impl BigInt")
|
|
||||||
} else {
|
|
||||||
unsafe { u32::to_napi_value(env, n as u32) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::String(s) => unsafe { String::to_napi_value(env, s) },
|
Value::String(s) => unsafe { String::to_napi_value(env, s) },
|
||||||
Value::Array(arr) => unsafe { Vec::<Value>::to_napi_value(env, arr) },
|
Value::Array(arr) => unsafe { Vec::<Value>::to_napi_value(env, arr) },
|
||||||
Value::Object(obj) => unsafe { Map::to_napi_value(env, obj) },
|
Value::Object(obj) => unsafe { Map::to_napi_value(env, obj) },
|
||||||
|
@ -37,12 +24,7 @@ impl FromNapiValue for Value {
|
||||||
let ty = type_of!(env, napi_val)?;
|
let ty = type_of!(env, napi_val)?;
|
||||||
let val = match ty {
|
let val = match ty {
|
||||||
ValueType::Boolean => Value::Bool(unsafe { bool::from_napi_value(env, napi_val)? }),
|
ValueType::Boolean => Value::Bool(unsafe { bool::from_napi_value(env, napi_val)? }),
|
||||||
ValueType::Number => {
|
ValueType::Number => Value::Number(unsafe { Number::from_napi_value(env, napi_val)? }),
|
||||||
return Err(Error::new(
|
|
||||||
Status::InvalidArg,
|
|
||||||
"Js Number is not be able to convert to rust.".to_owned(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
ValueType::String => Value::String(unsafe { String::from_napi_value(env, napi_val)? }),
|
ValueType::String => Value::String(unsafe { String::from_napi_value(env, napi_val)? }),
|
||||||
ValueType::Object => {
|
ValueType::Object => {
|
||||||
let mut is_arr = false;
|
let mut is_arr = false;
|
||||||
|
@ -59,7 +41,37 @@ impl FromNapiValue for Value {
|
||||||
}
|
}
|
||||||
#[cfg(feature = "napi6")]
|
#[cfg(feature = "napi6")]
|
||||||
ValueType::BigInt => todo!(),
|
ValueType::BigInt => todo!(),
|
||||||
_ => Value::Null,
|
ValueType::Null => Value::Null,
|
||||||
|
ValueType::Function => {
|
||||||
|
return Err(Error::new(
|
||||||
|
Status::InvalidArg,
|
||||||
|
"JS functions cannot be represented as a serde_json::Value".to_owned(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
ValueType::Undefined => {
|
||||||
|
return Err(Error::new(
|
||||||
|
Status::InvalidArg,
|
||||||
|
"undefined cannot be represented as a serde_json::Value".to_owned(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
ValueType::Symbol => {
|
||||||
|
return Err(Error::new(
|
||||||
|
Status::InvalidArg,
|
||||||
|
"JS symbols cannot be represented as a serde_json::Value".to_owned(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
ValueType::External => {
|
||||||
|
return Err(Error::new(
|
||||||
|
Status::InvalidArg,
|
||||||
|
"External JS objects cannot be represented as a serde_json::Value".to_owned(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(Error::new(
|
||||||
|
Status::InvalidArg,
|
||||||
|
"Unknown JS variables cannot be represented as a serde_json::Value".to_owned(),
|
||||||
|
))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(val)
|
Ok(val)
|
||||||
|
@ -96,3 +108,50 @@ impl FromNapiValue for Map<String, Value> {
|
||||||
Ok(map)
|
Ok(map)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToNapiValue for Number {
|
||||||
|
unsafe fn to_napi_value(env: sys::napi_env, n: Self) -> Result<sys::napi_value> {
|
||||||
|
if n.is_i64() {
|
||||||
|
unsafe { i64::to_napi_value(env, n.as_i64().unwrap()) }
|
||||||
|
} else if n.is_f64() {
|
||||||
|
unsafe { f64::to_napi_value(env, n.as_f64().unwrap()) }
|
||||||
|
} else {
|
||||||
|
let n = n.as_u64().unwrap();
|
||||||
|
if n > u32::MAX as u64 {
|
||||||
|
todo!("impl BigInt")
|
||||||
|
} else {
|
||||||
|
unsafe { u32::to_napi_value(env, n as u32) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromNapiValue for Number {
|
||||||
|
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
|
||||||
|
let n = unsafe { f64::from_napi_value(env, napi_val)? };
|
||||||
|
// Try to auto-convert to integers
|
||||||
|
let n = if n.trunc() == n {
|
||||||
|
if n >= 0.0f64 && n <= u32::MAX as f64 {
|
||||||
|
// This can be represented as u32
|
||||||
|
Some(Number::from(n as u32))
|
||||||
|
} else if n < 0.0f64 && n >= i32::MIN as f64 {
|
||||||
|
Some(Number::from(n as i32))
|
||||||
|
} else {
|
||||||
|
// must be a float
|
||||||
|
Number::from_f64(n)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// must be a float
|
||||||
|
Number::from_f64(n)
|
||||||
|
};
|
||||||
|
|
||||||
|
let n = n.ok_or_else(|| {
|
||||||
|
Error::new(
|
||||||
|
Status::InvalidArg,
|
||||||
|
"Failed to convert js number to serde_json::Number".to_owned(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -123,6 +123,7 @@ Generated by [AVA](https://avajs.dev).
|
||||||
}␊
|
}␊
|
||||||
export function readPackageJson(): PackageJson␊
|
export function readPackageJson(): PackageJson␊
|
||||||
export function getPackageJsonName(packageJson: PackageJson): string␊
|
export function getPackageJsonName(packageJson: PackageJson): string␊
|
||||||
|
export function testSerdeRoundtrip(data: any): any␊
|
||||||
export function contains(source: string, target: string): boolean␊
|
export function contains(source: string, target: string): boolean␊
|
||||||
export function concatStr(s: string): string␊
|
export function concatStr(s: string): string␊
|
||||||
export function concatUtf16(s: string): string␊
|
export function concatUtf16(s: string): string␊
|
||||||
|
|
Binary file not shown.
|
@ -80,6 +80,7 @@ import {
|
||||||
receiveMutClassOrNumber,
|
receiveMutClassOrNumber,
|
||||||
getStrFromObject,
|
getStrFromObject,
|
||||||
returnJsFunction,
|
returnJsFunction,
|
||||||
|
testSerdeRoundtrip,
|
||||||
} from '../'
|
} from '../'
|
||||||
|
|
||||||
test('export const', (t) => {
|
test('export const', (t) => {
|
||||||
|
@ -299,6 +300,34 @@ test('serde-json', (t) => {
|
||||||
t.is(getPackageJsonName(packageJson), 'napi-rs')
|
t.is(getPackageJsonName(packageJson), 'napi-rs')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('serde-roundtrip', (t) => {
|
||||||
|
t.is(testSerdeRoundtrip(1), 1)
|
||||||
|
t.is(testSerdeRoundtrip(1.2), 1.2)
|
||||||
|
t.is(testSerdeRoundtrip(-1), -1)
|
||||||
|
|
||||||
|
t.deepEqual(testSerdeRoundtrip([1, 1.2, -1]), [1, 1.2, -1])
|
||||||
|
t.deepEqual(testSerdeRoundtrip({ a: 1, b: 1.2, c: -1 }), {
|
||||||
|
a: 1,
|
||||||
|
b: 1.2,
|
||||||
|
c: -1,
|
||||||
|
})
|
||||||
|
t.throws(() => testSerdeRoundtrip(NaN))
|
||||||
|
|
||||||
|
t.is(testSerdeRoundtrip(null), null)
|
||||||
|
|
||||||
|
let err = t.throws(() => testSerdeRoundtrip(undefined))
|
||||||
|
t.is(err!.message, 'undefined cannot be represented as a serde_json::Value')
|
||||||
|
|
||||||
|
err = t.throws(() => testSerdeRoundtrip(() => {}))
|
||||||
|
t.is(
|
||||||
|
err!.message,
|
||||||
|
'JS functions cannot be represented as a serde_json::Value',
|
||||||
|
)
|
||||||
|
|
||||||
|
err = t.throws(() => testSerdeRoundtrip(Symbol.for('foo')))
|
||||||
|
t.is(err!.message, 'JS symbols cannot be represented as a serde_json::Value')
|
||||||
|
})
|
||||||
|
|
||||||
test('buffer', (t) => {
|
test('buffer', (t) => {
|
||||||
let buf = getBuffer()
|
let buf = getBuffer()
|
||||||
t.is(buf.toString('utf-8'), 'Hello world')
|
t.is(buf.toString('utf-8'), 'Hello world')
|
||||||
|
|
1
examples/napi/index.d.ts
vendored
1
examples/napi/index.d.ts
vendored
|
@ -113,6 +113,7 @@ export interface PackageJson {
|
||||||
}
|
}
|
||||||
export function readPackageJson(): PackageJson
|
export function readPackageJson(): PackageJson
|
||||||
export function getPackageJsonName(packageJson: PackageJson): string
|
export function getPackageJsonName(packageJson: PackageJson): string
|
||||||
|
export function testSerdeRoundtrip(data: any): any
|
||||||
export function contains(source: string, target: string): boolean
|
export function contains(source: string, target: string): boolean
|
||||||
export function concatStr(s: string): string
|
export function concatStr(s: string): string
|
||||||
export function concatUtf16(s: string): string
|
export function concatUtf16(s: string): string
|
||||||
|
|
|
@ -25,3 +25,8 @@ fn read_package_json() -> Result<PackageJson> {
|
||||||
fn get_package_json_name(package_json: PackageJson) -> String {
|
fn get_package_json_name(package_json: PackageJson) -> String {
|
||||||
package_json.name
|
package_json.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
fn test_serde_roundtrip(data: Value) -> Value {
|
||||||
|
data
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue