From 3983be23f5c7fb4dc82b491935ed30475153801d Mon Sep 17 00:00:00 2001 From: Gabriel Francisco Date: Thu, 23 Mar 2023 02:34:34 -0300 Subject: [PATCH] fix(napi): big numbers losing precision on serde_json::Value (#1538) * fix(napi): big numbers losing precision on serde_json::Value * fix(napi): add feature flags for bigint on number conversion * chore(napi): change MAX_SAFE_INT to constant and convert big numbers to string when bigint is not available * chore(napi): change how the check for safe integer range is made --- .../src/bindgen_runtime/js_values/serde.rs | 22 ++++++++++++++++-- examples/napi/__test__/typegen.spec.ts.md | 1 + examples/napi/__test__/typegen.spec.ts.snap | Bin 3877 -> 3892 bytes examples/napi/__test__/values.spec.ts | 18 ++++++++++++++ examples/napi/index.d.ts | 1 + examples/napi/src/serde.rs | 6 +++++ 6 files changed, 46 insertions(+), 2 deletions(-) diff --git a/crates/napi/src/bindgen_runtime/js_values/serde.rs b/crates/napi/src/bindgen_runtime/js_values/serde.rs index edff26da..52983850 100644 --- a/crates/napi/src/bindgen_runtime/js_values/serde.rs +++ b/crates/napi/src/bindgen_runtime/js_values/serde.rs @@ -4,6 +4,8 @@ use crate::{ bindgen_runtime::Null, check_status, sys, type_of, Error, JsObject, Result, Status, ValueType, }; +#[cfg(feature = "napi6")] +use super::BigInt; use super::{FromNapiValue, Object, ToNapiValue}; impl ToNapiValue for Value { @@ -111,14 +113,30 @@ impl FromNapiValue for Map { impl ToNapiValue for Number { unsafe fn to_napi_value(env: sys::napi_env, n: Self) -> Result { + #[cfg(feature = "napi6")] + const MAX_SAFE_INT: i64 = 9007199254740991i64; // 2 ^ 53 - 1 if n.is_i64() { - unsafe { i64::to_napi_value(env, n.as_i64().unwrap()) } + let n = n.as_i64().unwrap(); + #[cfg(feature = "napi6")] + { + if !(-MAX_SAFE_INT..=MAX_SAFE_INT).contains(&n) { + return unsafe { BigInt::to_napi_value(env, BigInt::from(n)) }; + } + } + + unsafe { i64::to_napi_value(env, n) } } 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") + #[cfg(feature = "napi6")] + { + return unsafe { BigInt::to_napi_value(env, BigInt::from(n)) }; + } + + #[cfg(not(feature = "napi6"))] + return unsafe { String::to_napi_value(env, n.to_string()) }; } else { unsafe { u32::to_napi_value(env, n as u32) } } diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index dcb71156..a907c780 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -195,6 +195,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 testSerdeBigNumberPrecision(number: string): any␊ export function returnFromSharedCrate(): Shared␊ export function contains(source: string, target: string): boolean␊ export function concatStr(s: string): string␊ diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index 51bfd98d7692effc510870ed6c2d14be2abb3702..14120495aadf57303e28e877b473bfd953caf790 100644 GIT binary patch literal 3892 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 Co`A;y literal 3877 zcmV+=58CiSRzVw(wq`ho9}f=?&yCd2 z(@C6$XX2+{nB+2M7k?4+w1^`zV+qTtWF$=UoQ0CeWyVOvrew-v#zjIipp@dLpQDIM z`ptv?{O*fCe1ZS{=J#Lx@wfkeaQg<4BIb$gM_kZJ%*c(~*PxN9#+yzfw$ehTH|>}a znJmfi;N{`X(~%-z7g?H162?>rvVS32p3wMY@*enlIDSk%ULz!DG)j|rNxuBDuNoRn zMZ&)?7!k|GB#qVQH_!X#^=+Swq3QD4r5y=4WcieajEv?q2Zj*w1C0yTCrPoGupDS_ z-?%}>b1n${PeD$JkjSlNR=w}-9}Hf-7=JfB8I8-XhGJSIq2y@-vX-yYJQ6{#PX>8T zmk))^c`|#fhIq=PAR=8b(_J`&xDdz1!a>lqk6VPISSUjHE*i8wFM~3DCP3VXo+_z( z#s|2dM0xYJlZpvg;5IG(Y0={f9)P+H#4-tkjLJDInGy~3e3_>UF4)7}Vmj@bDaB4l zMJ)OFJPl|*vx7N(eNvk68K}S@iUQgu^*A0EiwfyEpjH{?ag(;@`&Ah zFVf`tTXNZDgY0f?%Va;#)7<6dGc0z_fVbqCI4}*Uy1}hmw*VLn8%>sP-YN`uYVSY8 z;Hko}#$fz7uoQ<$R!G282xDNWqS+$O^jMx(v(t+V@&cO!9>twO9ioGu39P74aJ{g5 z(B|(E&hs=$_f)?l9>-ie#A1ns3JU){X~H zfV*naN+p%5m6Fro{R-@v(3+a&T0Cp#nhz zi|=LHvn&`woMhG#cW_u*__h`dlnzYOwBK6%ifC7xESj$(8XBS>+C*LaD!Wt8eE?kU z!r!4ktkQjVLC;R|(({<^mG%W5N7VBL-4w=HH+ohpEUDL5an8^uDW zE0CGwn8PI!tWgT7%H18IjSQIr0=d)5SXAHVJkdE1qVCfCOeGzJ?4>gZ2JUQ`FVDd^ z3gt!2_aT+=ecOH?Qz4h8XAz!ZR8g&mGu-|n13Dbfq*dRf1$@UmbOK)JjDS#pYnFi! zBhZr^ev*WpmlG?5p6;#g=?n`YQ_Oqz@eRiT?_n8*>_CM(_&CoQw%^5o8u%O;L9;rIDb{08HP>Uce*qVh8?GecXtBFA8X94$hp9ooGT|Ec(sNm zJoJ7wCdA|n!pWRGx*aOUAhMN(7^V^PBJ0S&=HY?hKQPB|H1cy_id6?OT;h6ywR3oF zpxMN@BReViVdlZdieKi7Lb?-M2egGc_&5AsPQFuKpqd<`5S491_?B;+c-x*j#|Us+#^ zeM-l>K}Q=#wgLff595e}tW{Onpw41JHaAjb4^_ESJ=D_%bJNiYXo6acmD%SOQ8bvJ zS6;o9qWXu_;Z?Z`httDEanT4BJ^{gs+W1gBT^>P@67df_Drh{&XD3OyHy{8#(IgQ|sxrsa?wC^NUAmoCx^dNURZ2O5Cb#S$ z({qOfob1|KLnnIxM#l`&oy+O=r8AX;B+FwWmgOH)V_k0GSt88mK>B{X@4atIL zCOQW-dg6qZ9a>#}!*`EnDudMPuOg|8kk2tvU0~JYsRHXdt25;j*ur7RgJeB1B<>PS z{d2YySZc6q*s8L7D<7*1-cRFnLX{BkY$TnMnQG`FT`vk%KyQoG$&upN8Zhm?R2*AV zZFr|M_GCrsV`9P<7K#V@wa4hx4|f7ils$NHI2b9e1~y69#fZ2oPg6UFMPT;}GZWAQ z1%T<>iWy9kQ$2^sK%fh#p=}Xnpu9yfbT^@Fh_RHSS8Q&#KsYb`8sds22C^#eZ0bVA z-&kPyHk{LBX3m~a!;D#CrB2qH?aDnVzptq2q16OOk+EDZk&ln|bW80FZNj#YWBdzj zRYx_XjjBn^#Ddm-Y!)N2VvWSlbjb_yyrj%^@qJ}^wRQ+s7I?i&)*MmWfsge;*Ohc@ z#AV4r1l`mnd2u4{E@r~aehf879|I5ZbQ+n}ukGyx=_}=l z2t-=sA*&(~NpqNk6W#j4K0pX5nu(1Pc18S3PPgxGPX2;Qo@{SIpIbR?$c_N}reg8K z1nq#1%bob#<4LtmGVy%R(znRY!D6L_9892f2y)KTI0n1q2_M!a9*DLH)D{tZrb+;c zdZb=9z(<3E)g&4TI(72L%FcKygdUS3TUg_&KBThJUyFADdm%o;vd`y*`;06O<5Uzm z8|u?Cby#q%I~P%vuPmFbLIgS0M~pCOZ_yz;5aYy&lPfuU5$p z@>vkaMHVy2#>5KN^#P{cVER+)t3#x}Dq_1Itffi2e^lfr~JO zzMf4Z+j7s)-P(F!F?I4yPY*1OVtsurH24Py1LUi_JFU8*XSyHee(!q)PN-x+-DE0Qwc)zLIYy3RPd zo6`_fJ-2FsbFTqJ3B=maVM!JYB9TZV3D8>}s&E1!95mc3ic_G%JW5sEdQN~XY-4j& zq{hS!sm+XpZF2B6e|VMAVN$;?@YdVS7!Rkybg^KG)a}S7<`-;{zUSn>|N7_ORXa}P ze4f$;uQBM(Ue6P}TW#9F>0UZh6Ysgw*Q0#}8&e%Ws>_QZgv=m7yuXNtc;ze1?fU}K z2PSkcIj54COQ@h-OoSA+t-XdCFZ-J8+c$4NUf%w;fBkwn1s$!QR7cu)-y)rLd{NF{ zopuc!O|Z93h_B>$q!vC%(tLr&tqSkcvS5gflprDIWSWvm=m-TvK;=^5LZ;E@Kyqf!07tKU@)J6fb?SYyRp zOW6Ig~Ya;0rluA-BDK&g0# zmrjO|=p$)}9diYCp({oUx}30M#-i$o04rB&c-Pc7R;5>;;T|w;I4@1J8Kq9>+W(p# zsi?JCa(3`#mpMh%-_zFw9Nq6yCT(h}vW!Q!wrYeeRXyg(dpa(18MCfI%oKTf z<51(Gdais?$A&7#AWJ-+PY|6*1^*{7X#e=7W}gyveoC`B%EaMA>-`ZGXTvns`@ZDI zY=~N$wjO8+&4n}kx2JzJRP31;2!Z#t9})e~SOWd=V~0|}zyXh(4Awx|DtzZSc9@P) ze1oX#`!A`SJ9eP4WVVp#ZpZ5St_cJp$NE);NBf^2^}=Xzt-WazjZIT-@PGrW{0G+3nsdBc zS>%ZNq^U_X->tu_5%~B`Rxsw)Gi4% zA^fteZ+^`{>(bj=mOF3lt?zcgCyMBeuA1HA68&qMzr{^`XkH%LMbmeOZIZAadgoVQ z8~N%yFc6HnCJnJW#=EO0T;w^c9Rwuc*E}lJAl|Cc_CAsO)@q)zENv}D#B+D6^+92RRmb;^UmwyIKcgtCkzB@X zG-pgjfG||m(3zohJDyrU&7_0VWYZySim@I-xpemI+^(&edHE~{#`%g2PirnN=33)xC#ZuLhfj}>_K(Mo#&_H`xclAxd%lYX zIy?fR?VSfzClwEMAmMNOtOWWR`PB1vF?7qp17^KPS7pWuh-t;xV-j1?xcwGDLDWDh{} { t.is(err!.message, 'JS symbols cannot be represented as a serde_json::Value') }) +test('serde-large-number-precision', (t) => { + t.is(testSerdeBigNumberPrecision('12345').number, 12345) + t.is( + testSerdeBigNumberPrecision('123456789012345678901234567890').number, + 1.2345678901234568e29, + ) + t.is( + testSerdeBigNumberPrecision('123456789012345678901234567890.123456789') + .number, + 1.2345678901234568e29, + ) + t.is( + testSerdeBigNumberPrecision('109775245175819965').number.toString(), + '109775245175819965', + ) +}) + test('buffer', (t) => { let buf = getBuffer() t.is(buf.toString('utf-8'), 'Hello world') diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index 1d04fd43..0abc1042 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -185,6 +185,7 @@ export interface PackageJson { export function readPackageJson(): PackageJson export function getPackageJsonName(packageJson: PackageJson): string export function testSerdeRoundtrip(data: any): any +export function testSerdeBigNumberPrecision(number: string): any export function returnFromSharedCrate(): Shared export function contains(source: string, target: string): boolean export function concatStr(s: string): string diff --git a/examples/napi/src/serde.rs b/examples/napi/src/serde.rs index b7a76177..13a52196 100644 --- a/examples/napi/src/serde.rs +++ b/examples/napi/src/serde.rs @@ -30,3 +30,9 @@ fn get_package_json_name(package_json: PackageJson) -> String { fn test_serde_roundtrip(data: Value) -> Value { data } + +#[napi] +fn test_serde_big_number_precision(number: String) -> Value { + let data = format!("{{\"number\":{}}}", number); + serde_json::from_str(&data).unwrap() +}