Skip to main content

dpp/serialization/json/
safe_integer.rs

1//! Serde `with` modules for bare u64/i64 fields and their Option variants.
2//!
3//! These are automatically added by the `#[json_safe_fields]` attribute macro.
4//! You should not normally need to reference them directly.
5//!
6//! ## Behavior
7//!
8//! - **JSON** (`is_human_readable() == true`): values > `MAX_SAFE_INTEGER` (2^53 - 1)
9//!   are serialized as strings. Deserialization accepts both numbers and strings.
10//! - **platform_value / bincode** (`is_human_readable() == false`): native integer
11//!   representation, no transformation.
12//!
13//! ## Available modules
14//!
15//! - [`json_safe_u64`] — for `u64` fields
16//! - [`json_safe_i64`] — for `i64` fields
17//! - [`json_safe_option_u64`] — for `Option<u64>` fields
18//! - [`json_safe_option_i64`] — for `Option<i64>` fields
19
20pub(crate) const JS_MAX_SAFE_INTEGER: u64 = 9_007_199_254_740_991;
21
22/// Serde `with` module for `u64` fields.
23pub mod json_safe_u64 {
24    use serde::de::{self, Deserializer, Visitor};
25    use serde::ser::Serializer;
26
27    use super::JS_MAX_SAFE_INTEGER;
28
29    pub fn serialize<S: Serializer>(value: &u64, serializer: S) -> Result<S::Ok, S::Error> {
30        if serializer.is_human_readable() && *value > JS_MAX_SAFE_INTEGER {
31            serializer.serialize_str(&value.to_string())
32        } else {
33            serializer.serialize_u64(*value)
34        }
35    }
36
37    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<u64, D::Error> {
38        if deserializer.is_human_readable() {
39            deserializer.deserialize_any(U64OrStringVisitor)
40        } else {
41            serde::Deserialize::deserialize(deserializer)
42        }
43    }
44
45    struct U64OrStringVisitor;
46
47    impl<'de> Visitor<'de> for U64OrStringVisitor {
48        type Value = u64;
49
50        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
51            formatter.write_str("a u64 or a string containing a u64")
52        }
53
54        fn visit_u64<E: de::Error>(self, v: u64) -> Result<Self::Value, E> {
55            Ok(v)
56        }
57
58        fn visit_i64<E: de::Error>(self, v: i64) -> Result<Self::Value, E> {
59            u64::try_from(v)
60                .map_err(|_| de::Error::custom(format!("i64 value {v} out of u64 range")))
61        }
62
63        fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
64            v.parse::<u64>()
65                .map_err(|_| de::Error::custom(format!("invalid u64 string: {v}")))
66        }
67    }
68}
69
70/// Serde `with` module for `u128` fields.
71///
72/// `u128` is never JS-safe as a bare number once it exceeds
73/// `Number.MAX_SAFE_INTEGER`, so the human-readable (JSON) path stringifies
74/// large values. The binary / `Value` path keeps the native `u128`.
75pub mod json_safe_u128 {
76    use serde::de::{self, Deserializer, Visitor};
77    use serde::ser::Serializer;
78
79    use super::JS_MAX_SAFE_INTEGER;
80
81    pub fn serialize<S: Serializer>(value: &u128, serializer: S) -> Result<S::Ok, S::Error> {
82        if serializer.is_human_readable() && *value > JS_MAX_SAFE_INTEGER as u128 {
83            serializer.serialize_str(&value.to_string())
84        } else {
85            serializer.serialize_u128(*value)
86        }
87    }
88
89    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<u128, D::Error> {
90        if deserializer.is_human_readable() {
91            deserializer.deserialize_any(U128OrStringVisitor)
92        } else {
93            serde::Deserialize::deserialize(deserializer)
94        }
95    }
96
97    struct U128OrStringVisitor;
98
99    impl<'de> Visitor<'de> for U128OrStringVisitor {
100        type Value = u128;
101
102        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
103            formatter.write_str("a u128 or a string containing a u128")
104        }
105
106        fn visit_u64<E: de::Error>(self, v: u64) -> Result<Self::Value, E> {
107            Ok(v as u128)
108        }
109
110        fn visit_u128<E: de::Error>(self, v: u128) -> Result<Self::Value, E> {
111            Ok(v)
112        }
113
114        fn visit_i64<E: de::Error>(self, v: i64) -> Result<Self::Value, E> {
115            u128::try_from(v)
116                .map_err(|_| de::Error::custom(format!("i64 value {v} out of u128 range")))
117        }
118
119        fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
120            v.parse::<u128>()
121                .map_err(|_| de::Error::custom(format!("invalid u128 string: {v}")))
122        }
123    }
124}
125
126/// Serde `with` module for a `u128` field that is buffered through serde's
127/// `Content` enum — i.e. a field of an internally-tagged (`#[serde(tag = "…")]`)
128/// enum or struct.
129///
130/// Same JS-safety as [`json_safe_u128`] (values above `Number.MAX_SAFE_INTEGER`
131/// stringify in human-readable JSON), but it **never** emits `serialize_u128`.
132/// serde's `Content` enum cannot hold a 128-bit integer in this serde version, so
133/// `json_safe_u128`'s `serialize_u128` round-trips to an "invalid type: integer …
134/// as u128" error once internal tagging buffers it. This variant instead encodes a
135/// plain number while the value fits in `u64`, and a string once it doesn't (and,
136/// in human-readable JSON, once it exceeds `Number.MAX_SAFE_INTEGER`). The
137/// `Value` / bincode paths keep the value lossless via the same number/string split.
138pub mod json_safe_u128_content {
139    use serde::de::{self, Visitor};
140    use serde::{Deserializer, Serializer};
141
142    use super::JS_MAX_SAFE_INTEGER;
143
144    pub fn serialize<S: Serializer>(value: &u128, serializer: S) -> Result<S::Ok, S::Error> {
145        let stringify_above = if serializer.is_human_readable() {
146            JS_MAX_SAFE_INTEGER as u128
147        } else {
148            u64::MAX as u128
149        };
150        if *value > stringify_above {
151            serializer.serialize_str(&value.to_string())
152        } else {
153            serializer.serialize_u64(*value as u64)
154        }
155    }
156
157    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<u128, D::Error> {
158        struct V;
159        impl Visitor<'_> for V {
160            type Value = u128;
161            fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
162                f.write_str("a u128 as a number or string")
163            }
164            fn visit_u64<E: de::Error>(self, v: u64) -> Result<u128, E> {
165                Ok(v as u128)
166            }
167            fn visit_u128<E: de::Error>(self, v: u128) -> Result<u128, E> {
168                Ok(v)
169            }
170            fn visit_str<E: de::Error>(self, v: &str) -> Result<u128, E> {
171                v.parse()
172                    .map_err(|_| de::Error::custom(format!("invalid u128 string: {v}")))
173            }
174        }
175        deserializer.deserialize_any(V)
176    }
177}
178
179/// Serde `with` module for `i64` fields.
180pub mod json_safe_i64 {
181    use serde::de::{self, Deserializer, Visitor};
182    use serde::ser::Serializer;
183
184    use super::JS_MAX_SAFE_INTEGER;
185
186    const JS_MIN_SAFE_INTEGER: i64 = -(JS_MAX_SAFE_INTEGER as i64);
187
188    pub fn serialize<S: Serializer>(value: &i64, serializer: S) -> Result<S::Ok, S::Error> {
189        if serializer.is_human_readable()
190            && (*value > JS_MAX_SAFE_INTEGER as i64 || *value < JS_MIN_SAFE_INTEGER)
191        {
192            serializer.serialize_str(&value.to_string())
193        } else {
194            serializer.serialize_i64(*value)
195        }
196    }
197
198    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<i64, D::Error> {
199        if deserializer.is_human_readable() {
200            deserializer.deserialize_any(I64OrStringVisitor)
201        } else {
202            serde::Deserialize::deserialize(deserializer)
203        }
204    }
205
206    struct I64OrStringVisitor;
207
208    impl<'de> Visitor<'de> for I64OrStringVisitor {
209        type Value = i64;
210
211        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
212            formatter.write_str("an i64 or a string containing an i64")
213        }
214
215        fn visit_i64<E: de::Error>(self, v: i64) -> Result<Self::Value, E> {
216            Ok(v)
217        }
218
219        fn visit_u64<E: de::Error>(self, v: u64) -> Result<Self::Value, E> {
220            i64::try_from(v)
221                .map_err(|_| de::Error::custom(format!("u64 value {v} out of i64 range")))
222        }
223
224        fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
225            v.parse::<i64>()
226                .map_err(|_| de::Error::custom(format!("invalid i64 string: {v}")))
227        }
228    }
229}
230
231/// Serde `with` module for `Option<u64>` fields.
232pub mod json_safe_option_u64 {
233    use serde::de::{self, Deserializer, Visitor};
234    use serde::ser::Serializer;
235
236    pub fn serialize<S: Serializer>(value: &Option<u64>, serializer: S) -> Result<S::Ok, S::Error> {
237        match value {
238            Some(v) => super::json_safe_u64::serialize(v, serializer),
239            None => serializer.serialize_none(),
240        }
241    }
242
243    pub fn deserialize<'de, D: Deserializer<'de>>(
244        deserializer: D,
245    ) -> Result<Option<u64>, D::Error> {
246        if deserializer.is_human_readable() {
247            deserializer.deserialize_option(OptionU64Visitor)
248        } else {
249            serde::Deserialize::deserialize(deserializer)
250        }
251    }
252
253    struct OptionU64Visitor;
254
255    impl<'de> Visitor<'de> for OptionU64Visitor {
256        type Value = Option<u64>;
257
258        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
259            formatter.write_str("null, a u64, or a string containing a u64")
260        }
261
262        fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
263            Ok(None)
264        }
265
266        fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
267            Ok(None)
268        }
269
270        fn visit_some<D: Deserializer<'de>>(
271            self,
272            deserializer: D,
273        ) -> Result<Self::Value, D::Error> {
274            super::json_safe_u64::deserialize(deserializer).map(Some)
275        }
276    }
277}
278
279/// Serde `with` module for `Option<i64>` fields.
280pub mod json_safe_option_i64 {
281    use serde::de::{self, Deserializer, Visitor};
282    use serde::ser::Serializer;
283
284    pub fn serialize<S: Serializer>(value: &Option<i64>, serializer: S) -> Result<S::Ok, S::Error> {
285        match value {
286            Some(v) => super::json_safe_i64::serialize(v, serializer),
287            None => serializer.serialize_none(),
288        }
289    }
290
291    pub fn deserialize<'de, D: Deserializer<'de>>(
292        deserializer: D,
293    ) -> Result<Option<i64>, D::Error> {
294        if deserializer.is_human_readable() {
295            deserializer.deserialize_option(OptionI64Visitor)
296        } else {
297            serde::Deserialize::deserialize(deserializer)
298        }
299    }
300
301    struct OptionI64Visitor;
302
303    impl<'de> Visitor<'de> for OptionI64Visitor {
304        type Value = Option<i64>;
305
306        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
307            formatter.write_str("null, an i64, or a string containing an i64")
308        }
309
310        fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
311            Ok(None)
312        }
313
314        fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
315            Ok(None)
316        }
317
318        fn visit_some<D: Deserializer<'de>>(
319            self,
320            deserializer: D,
321        ) -> Result<Self::Value, D::Error> {
322            super::json_safe_i64::deserialize(deserializer).map(Some)
323        }
324    }
325}
326
327/// Serde `with` module for `Option<(String, u64)>` fields.
328///
329/// Used by `DocumentCreateTransitionV0::prefunded_voting_balance`. The
330/// `json_safe_fields` macro can't auto-inject on tuple-inside-Option fields,
331/// so this is added explicitly via `serde(with = ...)`. JS-safety semantics
332/// match `json_safe_u64`: large u64 values become strings in HR; non-HR
333/// keeps native u64.
334pub mod json_safe_option_string_u64_tuple {
335    use serde::de::{self, Deserializer, SeqAccess, Visitor};
336    use serde::ser::{SerializeTuple, Serializer};
337
338    pub fn serialize<S: Serializer>(
339        value: &Option<(String, u64)>,
340        serializer: S,
341    ) -> Result<S::Ok, S::Error> {
342        match value {
343            Some((s, n)) => {
344                let stringify = serializer.is_human_readable() && *n > super::JS_MAX_SAFE_INTEGER;
345                let mut tup = serializer.serialize_tuple(2)?;
346                tup.serialize_element(s)?;
347                if stringify {
348                    tup.serialize_element(&n.to_string())?;
349                } else {
350                    tup.serialize_element(n)?;
351                }
352                tup.end()
353            }
354            None => serializer.serialize_none(),
355        }
356    }
357
358    pub fn deserialize<'de, D: Deserializer<'de>>(
359        deserializer: D,
360    ) -> Result<Option<(String, u64)>, D::Error> {
361        deserializer.deserialize_option(OptStringU64TupleVisitor)
362    }
363
364    struct OptStringU64TupleVisitor;
365
366    impl<'de> Visitor<'de> for OptStringU64TupleVisitor {
367        type Value = Option<(String, u64)>;
368
369        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
370            formatter.write_str("null or a 2-tuple [String, u64-or-string]")
371        }
372
373        fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
374            Ok(None)
375        }
376
377        fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
378            Ok(None)
379        }
380
381        fn visit_some<D: Deserializer<'de>>(
382            self,
383            deserializer: D,
384        ) -> Result<Self::Value, D::Error> {
385            deserializer
386                .deserialize_tuple(2, StringU64TupleVisitor)
387                .map(Some)
388        }
389
390        // Some self-describing formats (serde_json with deserialize_any) call
391        // visit_seq directly when the wire shape is an array — accept that too.
392        fn visit_seq<A: SeqAccess<'de>>(self, seq: A) -> Result<Self::Value, A::Error> {
393            StringU64TupleVisitor.visit_seq(seq).map(Some)
394        }
395    }
396
397    /// Newtype wrapper that delegates u64 deserialization to `json_safe_u64`,
398    /// accepting both numbers and strings in HR.
399    #[derive(serde::Deserialize)]
400    #[serde(transparent)]
401    struct SafeU64(#[serde(with = "super::json_safe_u64")] u64);
402
403    struct StringU64TupleVisitor;
404
405    impl<'de> Visitor<'de> for StringU64TupleVisitor {
406        type Value = (String, u64);
407
408        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
409            formatter.write_str("a 2-tuple [String, u64-or-string]")
410        }
411
412        fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
413            let s: String = seq
414                .next_element()?
415                .ok_or_else(|| de::Error::invalid_length(0, &"a 2-tuple"))?;
416            let n: SafeU64 = seq
417                .next_element()?
418                .ok_or_else(|| de::Error::invalid_length(1, &"a 2-tuple"))?;
419            Ok((s, n.0))
420        }
421    }
422}
423
424/// Serde `with` module for `Option<(u32, u32, Vec<u8>)>` fields used by the
425/// `SharedEncryptedNote` / `PrivateEncryptedNote` type aliases on token
426/// transitions.
427///
428/// In HR (JSON) the inner `Vec<u8>` is base64-encoded so the wire shape is
429/// `[u32, u32, "<base64>"]` instead of an array-of-numbers. In non-HR
430/// (platform_value, bincode) the bytes stay as raw bytes (`Value::Bytes`).
431/// The two `u32` indices are always JS-safe (well below `MAX_SAFE_INTEGER`)
432/// so they don't need special protection.
433pub mod json_safe_option_encrypted_note {
434    use serde::de::{self, Deserializer, SeqAccess, Visitor};
435    use serde::ser::{SerializeTuple, Serializer};
436
437    /// Wrapper that emits its byte payload via `serialize_bytes` (raw bytes)
438    /// rather than the default `Vec<u8>` Serialize (sequence of u8). Used in
439    /// the non-HR path so platform_value receives `Value::Bytes` and bincode
440    /// emits a length-prefixed byte buffer.
441    struct BytesAsBytes<'a>(&'a [u8]);
442
443    impl<'a> serde::Serialize for BytesAsBytes<'a> {
444        fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
445            s.serialize_bytes(self.0)
446        }
447    }
448
449    pub fn serialize<S: Serializer>(
450        value: &Option<(u32, u32, Vec<u8>)>,
451        serializer: S,
452    ) -> Result<S::Ok, S::Error> {
453        match value {
454            Some((a, b, bytes)) => {
455                let is_hr = serializer.is_human_readable();
456                let mut tup = serializer.serialize_tuple(3)?;
457                tup.serialize_element(a)?;
458                tup.serialize_element(b)?;
459                if is_hr {
460                    use base64::Engine;
461                    let s = base64::engine::general_purpose::STANDARD.encode(bytes);
462                    tup.serialize_element(&s)?;
463                } else {
464                    tup.serialize_element(&BytesAsBytes(bytes))?;
465                }
466                tup.end()
467            }
468            None => serializer.serialize_none(),
469        }
470    }
471
472    #[allow(clippy::type_complexity)]
473    pub fn deserialize<'de, D: Deserializer<'de>>(
474        deserializer: D,
475    ) -> Result<Option<(u32, u32, Vec<u8>)>, D::Error> {
476        deserializer.deserialize_option(OptEncryptedNoteVisitor)
477    }
478
479    struct OptEncryptedNoteVisitor;
480
481    impl<'de> Visitor<'de> for OptEncryptedNoteVisitor {
482        type Value = Option<(u32, u32, Vec<u8>)>;
483
484        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
485            formatter.write_str("null or a 3-tuple [u32, u32, base64-string-or-bytes]")
486        }
487
488        fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
489            Ok(None)
490        }
491
492        fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
493            Ok(None)
494        }
495
496        fn visit_some<D: Deserializer<'de>>(
497            self,
498            deserializer: D,
499        ) -> Result<Self::Value, D::Error> {
500            deserializer
501                .deserialize_tuple(3, EncryptedNoteVisitor)
502                .map(Some)
503        }
504
505        fn visit_seq<A: SeqAccess<'de>>(self, seq: A) -> Result<Self::Value, A::Error> {
506            EncryptedNoteVisitor.visit_seq(seq).map(Some)
507        }
508    }
509
510    /// Newtype wrapper that accepts either a base64 string (HR) or a byte
511    /// sequence (non-HR) and produces a `Vec<u8>`.
512    struct BytesField(Vec<u8>);
513
514    impl<'de> serde::Deserialize<'de> for BytesField {
515        fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
516            struct V;
517            impl<'de> Visitor<'de> for V {
518                type Value = Vec<u8>;
519
520                fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
521                    f.write_str("base64 string or byte sequence")
522                }
523
524                fn visit_str<E: de::Error>(self, s: &str) -> Result<Vec<u8>, E> {
525                    use base64::Engine;
526                    base64::engine::general_purpose::STANDARD
527                        .decode(s)
528                        .map_err(|e| E::custom(format!("invalid base64: {e}")))
529                }
530
531                fn visit_bytes<E: de::Error>(self, b: &[u8]) -> Result<Vec<u8>, E> {
532                    Ok(b.to_vec())
533                }
534
535                fn visit_byte_buf<E: de::Error>(self, b: Vec<u8>) -> Result<Vec<u8>, E> {
536                    Ok(b)
537                }
538
539                fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Vec<u8>, A::Error> {
540                    let mut out = Vec::new();
541                    while let Some(b) = seq.next_element::<u8>()? {
542                        out.push(b);
543                    }
544                    Ok(out)
545                }
546            }
547            // Use `deserialize_any` so we accept whichever path the deserializer
548            // takes (string for JSON, bytes for bincode/platform_value).
549            d.deserialize_any(V).map(BytesField)
550        }
551    }
552
553    struct EncryptedNoteVisitor;
554
555    impl<'de> Visitor<'de> for EncryptedNoteVisitor {
556        type Value = (u32, u32, Vec<u8>);
557
558        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
559            formatter.write_str("a 3-tuple [u32, u32, base64-string-or-bytes]")
560        }
561
562        fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
563            let a: u32 = seq
564                .next_element()?
565                .ok_or_else(|| de::Error::invalid_length(0, &"a 3-tuple"))?;
566            let b: u32 = seq
567                .next_element()?
568                .ok_or_else(|| de::Error::invalid_length(1, &"a 3-tuple"))?;
569            let bytes: BytesField = seq
570                .next_element()?
571                .ok_or_else(|| de::Error::invalid_length(2, &"a 3-tuple"))?;
572            Ok((a, b, bytes.0))
573        }
574    }
575}
576
577#[cfg(test)]
578mod tests {
579    use super::*;
580    use serde::{Deserialize, Serialize};
581
582    #[derive(Debug, PartialEq, Serialize, Deserialize)]
583    struct TestU64 {
584        #[serde(with = "json_safe_u64")]
585        value: u64,
586    }
587
588    #[derive(Debug, PartialEq, Serialize, Deserialize)]
589    struct TestU128 {
590        #[serde(with = "json_safe_u128")]
591        value: u128,
592    }
593
594    #[derive(Debug, PartialEq, Serialize, Deserialize)]
595    struct TestI64 {
596        #[serde(with = "json_safe_i64")]
597        value: i64,
598    }
599
600    #[derive(Debug, PartialEq, Serialize, Deserialize)]
601    struct TestOptionU64 {
602        #[serde(default, with = "json_safe_option_u64")]
603        value: Option<u64>,
604    }
605
606    #[derive(Debug, PartialEq, Serialize, Deserialize)]
607    struct TestOptionI64 {
608        #[serde(default, with = "json_safe_option_i64")]
609        value: Option<i64>,
610    }
611
612    #[test]
613    fn u64_small_value_stays_number() {
614        let t = TestU64 { value: 42 };
615        let json = serde_json::to_value(&t).unwrap();
616        assert!(json["value"].is_number());
617        assert_eq!(json["value"].as_u64().unwrap(), 42);
618
619        let restored: TestU64 = serde_json::from_value(json).unwrap();
620        assert_eq!(t, restored);
621    }
622
623    #[test]
624    fn u64_large_value_becomes_string() {
625        let t = TestU64 { value: u64::MAX };
626        let json = serde_json::to_value(&t).unwrap();
627        assert!(json["value"].is_string());
628        assert_eq!(json["value"].as_str().unwrap(), "18446744073709551615");
629
630        let restored: TestU64 = serde_json::from_value(json).unwrap();
631        assert_eq!(t, restored);
632    }
633
634    #[test]
635    fn u128_small_value_stays_number() {
636        let t = TestU128 { value: 42 };
637        let json = serde_json::to_value(&t).unwrap();
638        assert!(json["value"].is_number());
639        assert_eq!(json["value"].as_u64().unwrap(), 42);
640
641        let restored: TestU128 = serde_json::from_value(json).unwrap();
642        assert_eq!(t, restored);
643    }
644
645    #[test]
646    fn u128_large_value_becomes_string() {
647        // Above u64::MAX — only representable as a string in JS-safe JSON.
648        let t = TestU128 {
649            value: (u64::MAX as u128) + 1,
650        };
651        let json = serde_json::to_value(&t).unwrap();
652        assert!(json["value"].is_string());
653        assert_eq!(json["value"].as_str().unwrap(), "18446744073709551616");
654
655        let restored: TestU128 = serde_json::from_value(json).unwrap();
656        assert_eq!(t, restored);
657    }
658
659    #[test]
660    fn u128_value_round_trips_through_non_human_readable() {
661        // platform_value is non-human-readable → native u128, no stringification.
662        let t = TestU128 {
663            value: (u64::MAX as u128) + 12345,
664        };
665        let value = platform_value::to_value(&t).unwrap();
666        let restored: TestU128 = platform_value::from_value(value).unwrap();
667        assert_eq!(t, restored);
668    }
669
670    #[test]
671    fn u64_at_max_safe_integer_stays_number() {
672        let t = TestU64 {
673            value: JS_MAX_SAFE_INTEGER,
674        };
675        let json = serde_json::to_value(&t).unwrap();
676        assert!(json["value"].is_number());
677    }
678
679    #[test]
680    fn u64_above_max_safe_integer_becomes_string() {
681        let t = TestU64 {
682            value: JS_MAX_SAFE_INTEGER + 1,
683        };
684        let json = serde_json::to_value(&t).unwrap();
685        assert!(json["value"].is_string());
686    }
687
688    #[test]
689    fn i64_small_value_stays_number() {
690        let t = TestI64 { value: -42 };
691        let json = serde_json::to_value(&t).unwrap();
692        assert!(json["value"].is_number());
693
694        let restored: TestI64 = serde_json::from_value(json).unwrap();
695        assert_eq!(t, restored);
696    }
697
698    #[test]
699    fn i64_large_value_becomes_string() {
700        let t = TestI64 { value: i64::MAX };
701        let json = serde_json::to_value(&t).unwrap();
702        assert!(json["value"].is_string());
703
704        let restored: TestI64 = serde_json::from_value(json).unwrap();
705        assert_eq!(t, restored);
706    }
707
708    #[test]
709    fn i64_large_negative_becomes_string() {
710        let t = TestI64 { value: i64::MIN };
711        let json = serde_json::to_value(&t).unwrap();
712        assert!(json["value"].is_string());
713
714        let restored: TestI64 = serde_json::from_value(json).unwrap();
715        assert_eq!(t, restored);
716    }
717
718    #[test]
719    fn option_u64_none_round_trip() {
720        let t = TestOptionU64 { value: None };
721        let json = serde_json::to_value(&t).unwrap();
722        assert!(json["value"].is_null());
723
724        let restored: TestOptionU64 = serde_json::from_value(json).unwrap();
725        assert_eq!(t, restored);
726    }
727
728    #[test]
729    fn option_u64_large_round_trip() {
730        let t = TestOptionU64 {
731            value: Some(u64::MAX),
732        };
733        let json = serde_json::to_value(&t).unwrap();
734        assert!(json["value"].is_string());
735
736        let restored: TestOptionU64 = serde_json::from_value(json).unwrap();
737        assert_eq!(t, restored);
738    }
739
740    #[test]
741    fn platform_value_u64_stays_native() {
742        let t = TestU64 { value: u64::MAX };
743        let pv = platform_value::to_value(&t).unwrap();
744
745        // platform_value is non-human-readable, so u64 stays as u64
746        let restored: TestU64 = platform_value::from_value(pv).unwrap();
747        assert_eq!(t, restored);
748    }
749
750    #[test]
751    fn option_i64_none_round_trip() {
752        let t = TestOptionI64 { value: None };
753        let json = serde_json::to_value(&t).unwrap();
754        assert!(json["value"].is_null());
755
756        let restored: TestOptionI64 = serde_json::from_value(json).unwrap();
757        assert_eq!(t, restored);
758    }
759
760    #[test]
761    fn option_i64_large_round_trip() {
762        let t = TestOptionI64 {
763            value: Some(i64::MAX),
764        };
765        let json = serde_json::to_value(&t).unwrap();
766        assert!(json["value"].is_string());
767
768        let restored: TestOptionI64 = serde_json::from_value(json).unwrap();
769        assert_eq!(t, restored);
770    }
771
772    #[test]
773    fn option_i64_large_negative_round_trip() {
774        let t = TestOptionI64 {
775            value: Some(i64::MIN),
776        };
777        let json = serde_json::to_value(&t).unwrap();
778        assert!(json["value"].is_string());
779
780        let restored: TestOptionI64 = serde_json::from_value(json).unwrap();
781        assert_eq!(t, restored);
782    }
783
784    #[test]
785    fn option_i64_missing_field_deserializes_as_none() {
786        let json = serde_json::json!({});
787        let restored: TestOptionI64 = serde_json::from_value(json).unwrap();
788        assert_eq!(restored.value, None);
789    }
790
791    #[test]
792    fn option_u64_missing_field_deserializes_as_none() {
793        let json = serde_json::json!({});
794        let restored: TestOptionU64 = serde_json::from_value(json).unwrap();
795        assert_eq!(restored.value, None);
796    }
797
798    #[test]
799    fn u64_deserialize_from_i64_value() {
800        // Tests visit_i64 path: JSON number that fits in i64 parsed as u64
801        let json = serde_json::json!({"value": 42});
802        let restored: TestU64 = serde_json::from_value(json).unwrap();
803        assert_eq!(restored.value, 42);
804    }
805
806    #[test]
807    fn u64_deserialize_negative_i64_fails() {
808        // Tests visit_i64 error path: negative i64 can't become u64
809        let json = serde_json::json!({"value": -1});
810        let result = serde_json::from_value::<TestU64>(json);
811        assert!(result.is_err());
812        assert!(result.unwrap_err().to_string().contains("out of u64 range"));
813    }
814
815    #[test]
816    fn u64_deserialize_invalid_string_fails() {
817        let json = serde_json::json!({"value": "not_a_number"});
818        let result = serde_json::from_value::<TestU64>(json);
819        assert!(result.is_err());
820        assert!(result
821            .unwrap_err()
822            .to_string()
823            .contains("invalid u64 string"));
824    }
825
826    #[test]
827    fn i64_deserialize_u64_overflow_fails() {
828        // Tests visit_u64 error path: u64::MAX can't fit in i64
829        let json = serde_json::json!({"value": u64::MAX.to_string()});
830        // This goes through visit_str which parses as i64 — will fail
831        let result = serde_json::from_value::<TestI64>(json);
832        assert!(result.is_err());
833    }
834
835    #[test]
836    fn i64_deserialize_invalid_string_fails() {
837        let json = serde_json::json!({"value": "not_a_number"});
838        let result = serde_json::from_value::<TestI64>(json);
839        assert!(result.is_err());
840        assert!(result
841            .unwrap_err()
842            .to_string()
843            .contains("invalid i64 string"));
844    }
845
846    #[test]
847    fn platform_value_i64_stays_native() {
848        let t = TestI64 { value: i64::MAX };
849        let pv = platform_value::to_value(&t).unwrap();
850        let restored: TestI64 = platform_value::from_value(pv).unwrap();
851        assert_eq!(t, restored);
852    }
853
854    #[test]
855    fn platform_value_option_u64_round_trip() {
856        let t = TestOptionU64 {
857            value: Some(u64::MAX),
858        };
859        let pv = platform_value::to_value(&t).unwrap();
860        let restored: TestOptionU64 = platform_value::from_value(pv).unwrap();
861        assert_eq!(t, restored);
862    }
863
864    #[test]
865    fn platform_value_option_i64_round_trip() {
866        let t = TestOptionI64 {
867            value: Some(i64::MIN),
868        };
869        let pv = platform_value::to_value(&t).unwrap();
870        let restored: TestOptionI64 = platform_value::from_value(pv).unwrap();
871        assert_eq!(t, restored);
872    }
873
874    #[test]
875    fn option_u64_small_value_stays_number() {
876        let t = TestOptionU64 { value: Some(42) };
877        let json = serde_json::to_value(&t).unwrap();
878        assert!(json["value"].is_number());
879
880        let restored: TestOptionU64 = serde_json::from_value(json).unwrap();
881        assert_eq!(t, restored);
882    }
883
884    #[test]
885    fn option_i64_small_value_stays_number() {
886        let t = TestOptionI64 { value: Some(-42) };
887        let json = serde_json::to_value(&t).unwrap();
888        assert!(json["value"].is_number());
889
890        let restored: TestOptionI64 = serde_json::from_value(json).unwrap();
891        assert_eq!(t, restored);
892    }
893
894    #[test]
895    fn tagged_enum_with_u64_round_trip() {
896        #[derive(Debug, PartialEq, Serialize, Deserialize)]
897        #[serde(tag = "$formatVersion")]
898        enum Versioned {
899            #[serde(rename = "0")]
900            V0(TestU64),
901        }
902
903        let v = Versioned::V0(TestU64 { value: u64::MAX });
904        let json = serde_json::to_value(&v).unwrap();
905        assert_eq!(json["$formatVersion"], "0");
906        assert!(json["value"].is_string());
907
908        let restored: Versioned = serde_json::from_value(json).unwrap();
909        assert_eq!(v, restored);
910    }
911
912    // --- Additional edge-case tests for json_safe_option_i64 ---
913
914    #[test]
915    fn option_i64_some_safe_value_stays_number() {
916        let t = TestOptionI64 { value: Some(1000) };
917        let json = serde_json::to_value(&t).unwrap();
918        assert!(json["value"].is_number());
919        assert_eq!(json["value"].as_i64().unwrap(), 1000);
920
921        let restored: TestOptionI64 = serde_json::from_value(json).unwrap();
922        assert_eq!(t, restored);
923    }
924
925    #[test]
926    fn option_i64_some_unsafe_positive_becomes_string() {
927        // JS_MAX_SAFE_INTEGER + 1 as i64
928        let unsafe_val = (JS_MAX_SAFE_INTEGER + 1) as i64;
929        let t = TestOptionI64 {
930            value: Some(unsafe_val),
931        };
932        let json = serde_json::to_value(&t).unwrap();
933        assert!(json["value"].is_string());
934
935        let restored: TestOptionI64 = serde_json::from_value(json).unwrap();
936        assert_eq!(t, restored);
937    }
938
939    #[test]
940    fn option_i64_some_unsafe_negative_becomes_string() {
941        // -(JS_MAX_SAFE_INTEGER) - 1 is below the safe boundary
942        let unsafe_neg = -(JS_MAX_SAFE_INTEGER as i64) - 1;
943        let t = TestOptionI64 {
944            value: Some(unsafe_neg),
945        };
946        let json = serde_json::to_value(&t).unwrap();
947        assert!(json["value"].is_string());
948
949        let restored: TestOptionI64 = serde_json::from_value(json).unwrap();
950        assert_eq!(t, restored);
951    }
952
953    // --- Boundary tests ---
954
955    #[test]
956    fn u64_exactly_at_max_safe_integer_round_trip() {
957        let t = TestU64 {
958            value: JS_MAX_SAFE_INTEGER,
959        };
960        let json = serde_json::to_value(&t).unwrap();
961        assert!(json["value"].is_number());
962
963        let restored: TestU64 = serde_json::from_value(json).unwrap();
964        assert_eq!(t, restored);
965    }
966
967    #[test]
968    fn u64_one_above_max_safe_integer_round_trip() {
969        let t = TestU64 {
970            value: JS_MAX_SAFE_INTEGER + 1,
971        };
972        let json = serde_json::to_value(&t).unwrap();
973        assert!(json["value"].is_string());
974        assert_eq!(
975            json["value"].as_str().unwrap(),
976            (JS_MAX_SAFE_INTEGER + 1).to_string()
977        );
978
979        let restored: TestU64 = serde_json::from_value(json).unwrap();
980        assert_eq!(t, restored);
981    }
982
983    #[test]
984    fn i64_exactly_at_positive_safe_boundary_stays_number() {
985        let t = TestI64 {
986            value: JS_MAX_SAFE_INTEGER as i64,
987        };
988        let json = serde_json::to_value(&t).unwrap();
989        assert!(json["value"].is_number());
990
991        let restored: TestI64 = serde_json::from_value(json).unwrap();
992        assert_eq!(t, restored);
993    }
994
995    #[test]
996    fn i64_one_above_positive_safe_boundary_becomes_string() {
997        let t = TestI64 {
998            value: JS_MAX_SAFE_INTEGER as i64 + 1,
999        };
1000        let json = serde_json::to_value(&t).unwrap();
1001        assert!(json["value"].is_string());
1002
1003        let restored: TestI64 = serde_json::from_value(json).unwrap();
1004        assert_eq!(t, restored);
1005    }
1006
1007    #[test]
1008    fn i64_exactly_at_negative_safe_boundary_stays_number() {
1009        let t = TestI64 {
1010            value: -(JS_MAX_SAFE_INTEGER as i64),
1011        };
1012        let json = serde_json::to_value(&t).unwrap();
1013        assert!(json["value"].is_number());
1014
1015        let restored: TestI64 = serde_json::from_value(json).unwrap();
1016        assert_eq!(t, restored);
1017    }
1018
1019    #[test]
1020    fn i64_one_below_negative_safe_boundary_becomes_string() {
1021        let t = TestI64 {
1022            value: -(JS_MAX_SAFE_INTEGER as i64) - 1,
1023        };
1024        let json = serde_json::to_value(&t).unwrap();
1025        assert!(json["value"].is_string());
1026
1027        let restored: TestI64 = serde_json::from_value(json).unwrap();
1028        assert_eq!(t, restored);
1029    }
1030
1031    // --- Zero and negative value tests ---
1032
1033    #[test]
1034    fn u64_zero_stays_number() {
1035        let t = TestU64 { value: 0 };
1036        let json = serde_json::to_value(&t).unwrap();
1037        assert!(json["value"].is_number());
1038        assert_eq!(json["value"].as_u64().unwrap(), 0);
1039
1040        let restored: TestU64 = serde_json::from_value(json).unwrap();
1041        assert_eq!(t, restored);
1042    }
1043
1044    #[test]
1045    fn i64_zero_stays_number() {
1046        let t = TestI64 { value: 0 };
1047        let json = serde_json::to_value(&t).unwrap();
1048        assert!(json["value"].is_number());
1049        assert_eq!(json["value"].as_i64().unwrap(), 0);
1050
1051        let restored: TestI64 = serde_json::from_value(json).unwrap();
1052        assert_eq!(t, restored);
1053    }
1054
1055    #[test]
1056    fn i64_negative_one_stays_number() {
1057        let t = TestI64 { value: -1 };
1058        let json = serde_json::to_value(&t).unwrap();
1059        assert!(json["value"].is_number());
1060
1061        let restored: TestI64 = serde_json::from_value(json).unwrap();
1062        assert_eq!(t, restored);
1063    }
1064
1065    #[test]
1066    fn option_i64_zero_round_trip() {
1067        let t = TestOptionI64 { value: Some(0) };
1068        let json = serde_json::to_value(&t).unwrap();
1069        assert!(json["value"].is_number());
1070
1071        let restored: TestOptionI64 = serde_json::from_value(json).unwrap();
1072        assert_eq!(t, restored);
1073    }
1074
1075    #[test]
1076    fn option_u64_zero_round_trip() {
1077        let t = TestOptionU64 { value: Some(0) };
1078        let json = serde_json::to_value(&t).unwrap();
1079        assert!(json["value"].is_number());
1080
1081        let restored: TestOptionU64 = serde_json::from_value(json).unwrap();
1082        assert_eq!(t, restored);
1083    }
1084
1085    #[test]
1086    fn option_u64_at_max_safe_integer_stays_number() {
1087        let t = TestOptionU64 {
1088            value: Some(JS_MAX_SAFE_INTEGER),
1089        };
1090        let json = serde_json::to_value(&t).unwrap();
1091        assert!(json["value"].is_number());
1092
1093        let restored: TestOptionU64 = serde_json::from_value(json).unwrap();
1094        assert_eq!(t, restored);
1095    }
1096
1097    #[test]
1098    fn option_u64_above_max_safe_integer_becomes_string() {
1099        let t = TestOptionU64 {
1100            value: Some(JS_MAX_SAFE_INTEGER + 1),
1101        };
1102        let json = serde_json::to_value(&t).unwrap();
1103        assert!(json["value"].is_string());
1104
1105        let restored: TestOptionU64 = serde_json::from_value(json).unwrap();
1106        assert_eq!(t, restored);
1107    }
1108
1109    #[test]
1110    fn option_i64_at_positive_safe_boundary_stays_number() {
1111        let t = TestOptionI64 {
1112            value: Some(JS_MAX_SAFE_INTEGER as i64),
1113        };
1114        let json = serde_json::to_value(&t).unwrap();
1115        assert!(json["value"].is_number());
1116
1117        let restored: TestOptionI64 = serde_json::from_value(json).unwrap();
1118        assert_eq!(t, restored);
1119    }
1120
1121    #[test]
1122    fn option_i64_above_positive_safe_boundary_becomes_string() {
1123        let t = TestOptionI64 {
1124            value: Some(JS_MAX_SAFE_INTEGER as i64 + 1),
1125        };
1126        let json = serde_json::to_value(&t).unwrap();
1127        assert!(json["value"].is_string());
1128
1129        let restored: TestOptionI64 = serde_json::from_value(json).unwrap();
1130        assert_eq!(t, restored);
1131    }
1132
1133    #[test]
1134    fn option_i64_at_negative_safe_boundary_stays_number() {
1135        let t = TestOptionI64 {
1136            value: Some(-(JS_MAX_SAFE_INTEGER as i64)),
1137        };
1138        let json = serde_json::to_value(&t).unwrap();
1139        assert!(json["value"].is_number());
1140
1141        let restored: TestOptionI64 = serde_json::from_value(json).unwrap();
1142        assert_eq!(t, restored);
1143    }
1144
1145    #[test]
1146    fn option_i64_below_negative_safe_boundary_becomes_string() {
1147        let t = TestOptionI64 {
1148            value: Some(-(JS_MAX_SAFE_INTEGER as i64) - 1),
1149        };
1150        let json = serde_json::to_value(&t).unwrap();
1151        assert!(json["value"].is_string());
1152
1153        let restored: TestOptionI64 = serde_json::from_value(json).unwrap();
1154        assert_eq!(t, restored);
1155    }
1156
1157    #[test]
1158    fn platform_value_option_i64_none_round_trip() {
1159        let t = TestOptionI64 { value: None };
1160        let pv = platform_value::to_value(&t).unwrap();
1161        let restored: TestOptionI64 = platform_value::from_value(pv).unwrap();
1162        assert_eq!(t, restored);
1163    }
1164
1165    #[test]
1166    fn platform_value_option_u64_none_round_trip() {
1167        let t = TestOptionU64 { value: None };
1168        let pv = platform_value::to_value(&t).unwrap();
1169        let restored: TestOptionU64 = platform_value::from_value(pv).unwrap();
1170        assert_eq!(t, restored);
1171    }
1172
1173    #[test]
1174    fn u64_deserialize_from_string_number() {
1175        // Deserialize a string-encoded number (even one that fits in a number)
1176        let json = serde_json::json!({"value": "42"});
1177        let restored: TestU64 = serde_json::from_value(json).unwrap();
1178        assert_eq!(restored.value, 42);
1179    }
1180
1181    #[test]
1182    fn i64_deserialize_from_string_number() {
1183        let json = serde_json::json!({"value": "-12345"});
1184        let restored: TestI64 = serde_json::from_value(json).unwrap();
1185        assert_eq!(restored.value, -12345);
1186    }
1187}