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:
AlCalzone 2022-02-06 04:58:17 +01:00 committed by GitHub
parent c001038852
commit ed12bd76bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 117 additions and 22 deletions

View file

@ -1,4 +1,4 @@
use serde_json::{Map, Value};
use serde_json::{Map, Number, Value};
use crate::{
bindgen_runtime::Null, check_status, sys, type_of, Error, JsObject, Result, Status, ValueType,
@ -11,20 +11,7 @@ impl ToNapiValue for Value {
match val {
Value::Null => unsafe { Null::to_napi_value(env, Null) },
Value::Bool(b) => unsafe { bool::to_napi_value(env, b) },
Value::Number(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::Number(n) => unsafe { Number::to_napi_value(env, n) },
Value::String(s) => unsafe { String::to_napi_value(env, s) },
Value::Array(arr) => unsafe { Vec::<Value>::to_napi_value(env, arr) },
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 val = match ty {
ValueType::Boolean => Value::Bool(unsafe { bool::from_napi_value(env, napi_val)? }),
ValueType::Number => {
return Err(Error::new(
Status::InvalidArg,
"Js Number is not be able to convert to rust.".to_owned(),
));
}
ValueType::Number => Value::Number(unsafe { Number::from_napi_value(env, napi_val)? }),
ValueType::String => Value::String(unsafe { String::from_napi_value(env, napi_val)? }),
ValueType::Object => {
let mut is_arr = false;
@ -59,7 +41,37 @@ impl FromNapiValue for Value {
}
#[cfg(feature = "napi6")]
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)
@ -96,3 +108,50 @@ impl FromNapiValue for Map<String, Value> {
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)
}
}

View file

@ -123,6 +123,7 @@ Generated by [AVA](https://avajs.dev).
}␊
export function readPackageJson(): PackageJson␊
export function getPackageJsonName(packageJson: PackageJson): string␊
export function testSerdeRoundtrip(data: any): any␊
export function contains(source: string, target: string): boolean␊
export function concatStr(s: string): string␊
export function concatUtf16(s: string): string␊

View file

@ -80,6 +80,7 @@ import {
receiveMutClassOrNumber,
getStrFromObject,
returnJsFunction,
testSerdeRoundtrip,
} from '../'
test('export const', (t) => {
@ -299,6 +300,34 @@ test('serde-json', (t) => {
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) => {
let buf = getBuffer()
t.is(buf.toString('utf-8'), 'Hello world')

View file

@ -113,6 +113,7 @@ export interface PackageJson {
}
export function readPackageJson(): PackageJson
export function getPackageJsonName(packageJson: PackageJson): string
export function testSerdeRoundtrip(data: any): any
export function contains(source: string, target: string): boolean
export function concatStr(s: string): string
export function concatUtf16(s: string): string

View file

@ -25,3 +25,8 @@ fn read_package_json() -> Result<PackageJson> {
fn get_package_json_name(package_json: PackageJson) -> String {
package_json.name
}
#[napi]
fn test_serde_roundtrip(data: Value) -> Value {
data
}