Skip to main content

hyperstack_sdk/
serde_utils.rs

1//! Serde helpers for deserializing integers that may arrive as JSON strings.
2//!
3//! The HyperStack server converts u64 values exceeding JavaScript's
4//! `Number.MAX_SAFE_INTEGER` (2^53 - 1) to strings for JSON transport.
5//! These helpers allow the Rust SDK to transparently parse both formats.
6//!
7//! Each function is designed for use with `#[serde(deserialize_with = "...")]`.
8
9use serde::de::{self, Deserializer, SeqAccess, Visitor};
10use std::fmt;
11
12// ─── Core visitors ──────────────────────────────────────────────────────────
13
14struct U64OrStringVisitor;
15
16impl<'de> Visitor<'de> for U64OrStringVisitor {
17    type Value = u64;
18
19    fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
20        f.write_str("u64 or string-encoded u64")
21    }
22
23    fn visit_u64<E: de::Error>(self, v: u64) -> Result<u64, E> {
24        Ok(v)
25    }
26
27    fn visit_i64<E: de::Error>(self, v: i64) -> Result<u64, E> {
28        u64::try_from(v).map_err(|_| E::custom(format!("negative value {v} cannot be u64")))
29    }
30
31    fn visit_f64<E: de::Error>(self, v: f64) -> Result<u64, E> {
32        if v >= 0.0 && v <= u64::MAX as f64 {
33            Ok(v as u64)
34        } else {
35            Err(E::custom(format!("f64 {v} out of u64 range")))
36        }
37    }
38
39    fn visit_str<E: de::Error>(self, v: &str) -> Result<u64, E> {
40        v.parse().map_err(E::custom)
41    }
42}
43
44struct I64OrStringVisitor;
45
46impl<'de> Visitor<'de> for I64OrStringVisitor {
47    type Value = i64;
48
49    fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
50        f.write_str("i64 or string-encoded i64")
51    }
52
53    fn visit_u64<E: de::Error>(self, v: u64) -> Result<i64, E> {
54        i64::try_from(v).map_err(|_| E::custom(format!("u64 {v} overflows i64")))
55    }
56
57    fn visit_i64<E: de::Error>(self, v: i64) -> Result<i64, E> {
58        Ok(v)
59    }
60
61    fn visit_f64<E: de::Error>(self, v: f64) -> Result<i64, E> {
62        if v >= i64::MIN as f64 && v <= i64::MAX as f64 {
63            Ok(v as i64)
64        } else {
65            Err(E::custom(format!("f64 {v} out of i64 range")))
66        }
67    }
68
69    fn visit_str<E: de::Error>(self, v: &str) -> Result<i64, E> {
70        v.parse().map_err(E::custom)
71    }
72}
73
74// ─── Bare types ─────────────────────────────────────────────────────────────
75
76/// Deserialize a bare `u64` from a JSON number or string.
77pub fn deserialize_u64<'de, D: Deserializer<'de>>(d: D) -> Result<u64, D::Error> {
78    d.deserialize_any(U64OrStringVisitor)
79}
80
81/// Deserialize a bare `i64` from a JSON number or string.
82pub fn deserialize_i64<'de, D: Deserializer<'de>>(d: D) -> Result<i64, D::Error> {
83    d.deserialize_any(I64OrStringVisitor)
84}
85
86// ─── Option<T> ──────────────────────────────────────────────────────────────
87// Used for non-optional spec fields. `None` = not yet received in any patch.
88// With `#[serde(default)]`, missing fields → None. This function is only
89// called when the field IS present in the JSON (null, number, or string).
90
91/// Deserialize `Option<u64>` from null / number / string.
92pub fn deserialize_option_u64<'de, D: Deserializer<'de>>(d: D) -> Result<Option<u64>, D::Error> {
93    struct V;
94    impl<'de> Visitor<'de> for V {
95        type Value = Option<u64>;
96        fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
97            f.write_str("null, u64, or string-encoded u64")
98        }
99        fn visit_unit<E: de::Error>(self) -> Result<Option<u64>, E> {
100            Ok(None)
101        }
102        fn visit_none<E: de::Error>(self) -> Result<Option<u64>, E> {
103            Ok(None)
104        }
105        fn visit_some<D2: Deserializer<'de>>(self, d: D2) -> Result<Option<u64>, D2::Error> {
106            deserialize_u64(d).map(Some)
107        }
108    }
109    d.deserialize_option(V)
110}
111
112/// Deserialize `Option<i64>` from null / number / string.
113pub fn deserialize_option_i64<'de, D: Deserializer<'de>>(d: D) -> Result<Option<i64>, D::Error> {
114    struct V;
115    impl<'de> Visitor<'de> for V {
116        type Value = Option<i64>;
117        fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
118            f.write_str("null, i64, or string-encoded i64")
119        }
120        fn visit_unit<E: de::Error>(self) -> Result<Option<i64>, E> {
121            Ok(None)
122        }
123        fn visit_none<E: de::Error>(self) -> Result<Option<i64>, E> {
124            Ok(None)
125        }
126        fn visit_some<D2: Deserializer<'de>>(self, d: D2) -> Result<Option<i64>, D2::Error> {
127            deserialize_i64(d).map(Some)
128        }
129    }
130    d.deserialize_option(V)
131}
132
133// ─── Option<Option<T>> ─────────────────────────────────────────────────────
134// Used for optional spec fields (patch semantics):
135//   None         = field not present in patch (handled by #[serde(default)])
136//   Some(None)   = field explicitly set to null
137//   Some(Some(v))= field has value
138//
139// This function is only called when the field IS present, so:
140//   JSON null   → Some(None)
141//   JSON number → Some(Some(n))
142//   JSON string → Some(Some(parse(s)))
143
144/// Deserialize `Option<Option<u64>>` for patch semantics.
145pub fn deserialize_option_option_u64<'de, D: Deserializer<'de>>(
146    d: D,
147) -> Result<Option<Option<u64>>, D::Error> {
148    struct V;
149    impl<'de> Visitor<'de> for V {
150        type Value = Option<Option<u64>>;
151        fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
152            f.write_str("null, u64, or string-encoded u64")
153        }
154        fn visit_unit<E: de::Error>(self) -> Result<Option<Option<u64>>, E> {
155            Ok(Some(None))
156        }
157        fn visit_u64<E: de::Error>(self, v: u64) -> Result<Option<Option<u64>>, E> {
158            Ok(Some(Some(v)))
159        }
160        fn visit_i64<E: de::Error>(self, v: i64) -> Result<Option<Option<u64>>, E> {
161            u64::try_from(v)
162                .map(|v| Some(Some(v)))
163                .map_err(|_| E::custom(format!("negative value {v} cannot be u64")))
164        }
165        fn visit_f64<E: de::Error>(self, v: f64) -> Result<Option<Option<u64>>, E> {
166            if v >= 0.0 && v < (u64::MAX as f64) {
167                Ok(Some(Some(v as u64)))
168            } else {
169                Err(E::custom(format!("f64 {v} out of u64 range")))
170            }
171        }
172        fn visit_str<E: de::Error>(self, v: &str) -> Result<Option<Option<u64>>, E> {
173            v.parse().map(|v| Some(Some(v))).map_err(E::custom)
174        }
175    }
176    d.deserialize_any(V)
177}
178
179/// Deserialize `Option<Option<i64>>` for patch semantics.
180pub fn deserialize_option_option_i64<'de, D: Deserializer<'de>>(
181    d: D,
182) -> Result<Option<Option<i64>>, D::Error> {
183    struct V;
184    impl<'de> Visitor<'de> for V {
185        type Value = Option<Option<i64>>;
186        fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
187            f.write_str("null, i64, or string-encoded i64")
188        }
189        fn visit_unit<E: de::Error>(self) -> Result<Option<Option<i64>>, E> {
190            Ok(Some(None))
191        }
192        fn visit_u64<E: de::Error>(self, v: u64) -> Result<Option<Option<i64>>, E> {
193            i64::try_from(v)
194                .map(|v| Some(Some(v)))
195                .map_err(|_| E::custom(format!("u64 {v} overflows i64")))
196        }
197        fn visit_i64<E: de::Error>(self, v: i64) -> Result<Option<Option<i64>>, E> {
198            Ok(Some(Some(v)))
199        }
200        fn visit_f64<E: de::Error>(self, v: f64) -> Result<Option<Option<i64>>, E> {
201            Ok(Some(Some(v as i64)))
202        }
203        fn visit_str<E: de::Error>(self, v: &str) -> Result<Option<Option<i64>>, E> {
204            v.parse().map(|v| Some(Some(v))).map_err(E::custom)
205        }
206    }
207    d.deserialize_any(V)
208}
209
210// ─── Vec<T> variants ────────────────────────────────────────────────────────
211// For array fields where elements may be numbers or strings.
212
213/// Deserialize `Option<Vec<u64>>` where each element may be a number or string.
214pub fn deserialize_option_vec_u64<'de, D: Deserializer<'de>>(
215    d: D,
216) -> Result<Option<Vec<u64>>, D::Error> {
217    struct V;
218    impl<'de> Visitor<'de> for V {
219        type Value = Option<Vec<u64>>;
220        fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
221            f.write_str("null or array of u64/string-encoded u64")
222        }
223        fn visit_unit<E: de::Error>(self) -> Result<Option<Vec<u64>>, E> {
224            Ok(None)
225        }
226        fn visit_none<E: de::Error>(self) -> Result<Option<Vec<u64>>, E> {
227            Ok(None)
228        }
229        fn visit_some<D2: Deserializer<'de>>(self, d: D2) -> Result<Option<Vec<u64>>, D2::Error> {
230            struct SeqV;
231            impl<'de> Visitor<'de> for SeqV {
232                type Value = Vec<u64>;
233                fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
234                    f.write_str("array of u64/string-encoded u64")
235                }
236                fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Vec<u64>, A::Error> {
237                    let mut vec = Vec::with_capacity(seq.size_hint().unwrap_or(0));
238                    while let Some(elem) = seq.next_element::<serde_json::Value>()? {
239                        let n = match &elem {
240                            serde_json::Value::Number(n) => n
241                                .as_u64()
242                                .or_else(|| n.as_i64().and_then(|i| u64::try_from(i).ok()))
243                                .ok_or_else(|| {
244                                    de::Error::custom(format!("cannot convert {n} to u64"))
245                                })?,
246                            serde_json::Value::String(s) => {
247                                s.parse::<u64>().map_err(de::Error::custom)?
248                            }
249                            other => {
250                                return Err(de::Error::custom(format!(
251                                    "expected number or string in array, got {other}"
252                                )));
253                            }
254                        };
255                        vec.push(n);
256                    }
257                    Ok(vec)
258                }
259            }
260            d.deserialize_seq(SeqV).map(Some)
261        }
262    }
263    d.deserialize_option(V)
264}
265
266/// Deserialize `Option<Option<Vec<u64>>>` for optional array fields (patch semantics).
267pub fn deserialize_option_option_vec_u64<'de, D: Deserializer<'de>>(
268    d: D,
269) -> Result<Option<Option<Vec<u64>>>, D::Error> {
270    struct V;
271    impl<'de> Visitor<'de> for V {
272        type Value = Option<Option<Vec<u64>>>;
273        fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
274            f.write_str("null or array of u64/string-encoded u64")
275        }
276        fn visit_unit<E: de::Error>(self) -> Result<Option<Option<Vec<u64>>>, E> {
277            Ok(Some(None))
278        }
279        fn visit_seq<A: SeqAccess<'de>>(
280            self,
281            mut seq: A,
282        ) -> Result<Option<Option<Vec<u64>>>, A::Error> {
283            let mut vec = Vec::with_capacity(seq.size_hint().unwrap_or(0));
284            while let Some(elem) = seq.next_element::<serde_json::Value>()? {
285                let n = match &elem {
286                    serde_json::Value::Number(n) => n
287                        .as_u64()
288                        .or_else(|| n.as_i64().and_then(|i| u64::try_from(i).ok()))
289                        .ok_or_else(|| de::Error::custom(format!("cannot convert {n} to u64")))?,
290                    serde_json::Value::String(s) => s.parse::<u64>().map_err(de::Error::custom)?,
291                    other => {
292                        return Err(de::Error::custom(format!(
293                            "expected number or string in array, got {other}"
294                        )));
295                    }
296                };
297                vec.push(n);
298            }
299            Ok(Some(Some(vec)))
300        }
301    }
302    d.deserialize_any(V)
303}
304
305/// Deserialize `Option<Vec<i64>>` where each element may be a number or string.
306pub fn deserialize_option_vec_i64<'de, D: Deserializer<'de>>(
307    d: D,
308) -> Result<Option<Vec<i64>>, D::Error> {
309    struct V;
310    impl<'de> Visitor<'de> for V {
311        type Value = Option<Vec<i64>>;
312        fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
313            f.write_str("null or array of i64/string-encoded i64")
314        }
315        fn visit_unit<E: de::Error>(self) -> Result<Option<Vec<i64>>, E> {
316            Ok(None)
317        }
318        fn visit_none<E: de::Error>(self) -> Result<Option<Vec<i64>>, E> {
319            Ok(None)
320        }
321        fn visit_some<D2: Deserializer<'de>>(self, d: D2) -> Result<Option<Vec<i64>>, D2::Error> {
322            struct SeqV;
323            impl<'de> Visitor<'de> for SeqV {
324                type Value = Vec<i64>;
325                fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
326                    f.write_str("array of i64/string-encoded i64")
327                }
328                fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Vec<i64>, A::Error> {
329                    let mut vec = Vec::with_capacity(seq.size_hint().unwrap_or(0));
330                    while let Some(elem) = seq.next_element::<serde_json::Value>()? {
331                        let n = match &elem {
332                            serde_json::Value::Number(n) => n.as_i64().ok_or_else(|| {
333                                de::Error::custom(format!("cannot convert {n} to i64"))
334                            })?,
335                            serde_json::Value::String(s) => {
336                                s.parse::<i64>().map_err(de::Error::custom)?
337                            }
338                            other => {
339                                return Err(de::Error::custom(format!(
340                                    "expected number or string in array, got {other}"
341                                )));
342                            }
343                        };
344                        vec.push(n);
345                    }
346                    Ok(vec)
347                }
348            }
349            d.deserialize_seq(SeqV).map(Some)
350        }
351    }
352    d.deserialize_option(V)
353}
354
355/// Deserialize `Option<Option<Vec<i64>>>` for optional array fields (patch semantics).
356pub fn deserialize_option_option_vec_i64<'de, D: Deserializer<'de>>(
357    d: D,
358) -> Result<Option<Option<Vec<i64>>>, D::Error> {
359    struct V;
360    impl<'de> Visitor<'de> for V {
361        type Value = Option<Option<Vec<i64>>>;
362        fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
363            f.write_str("null or array of i64/string-encoded i64")
364        }
365        fn visit_unit<E: de::Error>(self) -> Result<Option<Option<Vec<i64>>>, E> {
366            Ok(Some(None))
367        }
368        fn visit_seq<A: SeqAccess<'de>>(
369            self,
370            mut seq: A,
371        ) -> Result<Option<Option<Vec<i64>>>, A::Error> {
372            let mut vec = Vec::with_capacity(seq.size_hint().unwrap_or(0));
373            while let Some(elem) = seq.next_element::<serde_json::Value>()? {
374                let n = match &elem {
375                    serde_json::Value::Number(n) => n
376                        .as_i64()
377                        .ok_or_else(|| de::Error::custom(format!("cannot convert {n} to i64")))?,
378                    serde_json::Value::String(s) => s.parse::<i64>().map_err(de::Error::custom)?,
379                    other => {
380                        return Err(de::Error::custom(format!(
381                            "expected number or string in array, got {other}"
382                        )));
383                    }
384                };
385                vec.push(n);
386            }
387            Ok(Some(Some(vec)))
388        }
389    }
390    d.deserialize_any(V)
391}
392
393// ─── 32-bit narrowing helpers ───────────────────────────────────────────────
394// Delegate to the 64-bit deserializers above, then narrow via TryFrom.
395// This avoids duplicating all the visitor boilerplate for i32/u32.
396
397fn narrow_opt<W, N, E: de::Error>(opt: Option<W>) -> Result<Option<N>, E>
398where
399    N: TryFrom<W>,
400    N::Error: fmt::Display,
401{
402    opt.map(|v| N::try_from(v).map_err(E::custom)).transpose()
403}
404
405fn narrow_opt_opt<W, N, E: de::Error>(opt: Option<Option<W>>) -> Result<Option<Option<N>>, E>
406where
407    N: TryFrom<W>,
408    N::Error: fmt::Display,
409{
410    match opt {
411        None => Ok(None),
412        Some(None) => Ok(Some(None)),
413        Some(Some(v)) => N::try_from(v).map(|n| Some(Some(n))).map_err(E::custom),
414    }
415}
416
417fn narrow_opt_vec<W, N, E: de::Error>(opt: Option<Vec<W>>) -> Result<Option<Vec<N>>, E>
418where
419    N: TryFrom<W>,
420    N::Error: fmt::Display,
421{
422    opt.map(|vec| {
423        vec.into_iter()
424            .map(|v| N::try_from(v).map_err(E::custom))
425            .collect()
426    })
427    .transpose()
428}
429
430fn narrow_opt_opt_vec<W, N, E: de::Error>(
431    opt: Option<Option<Vec<W>>>,
432) -> Result<Option<Option<Vec<N>>>, E>
433where
434    N: TryFrom<W>,
435    N::Error: fmt::Display,
436{
437    match opt {
438        None => Ok(None),
439        Some(None) => Ok(Some(None)),
440        Some(Some(vec)) => vec
441            .into_iter()
442            .map(|v| N::try_from(v).map_err(E::custom))
443            .collect::<Result<Vec<N>, E>>()
444            .map(|v| Some(Some(v))),
445    }
446}
447
448// ─── Option<u32/i32> ────────────────────────────────────────────────────────
449
450pub fn deserialize_option_u32<'de, D: Deserializer<'de>>(d: D) -> Result<Option<u32>, D::Error> {
451    narrow_opt(deserialize_option_u64(d)?)
452}
453
454pub fn deserialize_option_i32<'de, D: Deserializer<'de>>(d: D) -> Result<Option<i32>, D::Error> {
455    narrow_opt(deserialize_option_i64(d)?)
456}
457
458// ─── Option<Option<u32/i32>> ────────────────────────────────────────────────
459
460pub fn deserialize_option_option_u32<'de, D: Deserializer<'de>>(
461    d: D,
462) -> Result<Option<Option<u32>>, D::Error> {
463    narrow_opt_opt(deserialize_option_option_u64(d)?)
464}
465
466pub fn deserialize_option_option_i32<'de, D: Deserializer<'de>>(
467    d: D,
468) -> Result<Option<Option<i32>>, D::Error> {
469    narrow_opt_opt(deserialize_option_option_i64(d)?)
470}
471
472// ─── Option<Vec<u32/i32>> ───────────────────────────────────────────────────
473
474pub fn deserialize_option_vec_u32<'de, D: Deserializer<'de>>(
475    d: D,
476) -> Result<Option<Vec<u32>>, D::Error> {
477    narrow_opt_vec(deserialize_option_vec_u64(d)?)
478}
479
480pub fn deserialize_option_vec_i32<'de, D: Deserializer<'de>>(
481    d: D,
482) -> Result<Option<Vec<i32>>, D::Error> {
483    narrow_opt_vec(deserialize_option_vec_i64(d)?)
484}
485
486// ─── Option<Option<Vec<u32/i32>>> ───────────────────────────────────────────
487
488pub fn deserialize_option_option_vec_u32<'de, D: Deserializer<'de>>(
489    d: D,
490) -> Result<Option<Option<Vec<u32>>>, D::Error> {
491    narrow_opt_opt_vec(deserialize_option_option_vec_u64(d)?)
492}
493
494pub fn deserialize_option_option_vec_i32<'de, D: Deserializer<'de>>(
495    d: D,
496) -> Result<Option<Option<Vec<i32>>>, D::Error> {
497    narrow_opt_opt_vec(deserialize_option_option_vec_i64(d)?)
498}
499
500#[cfg(test)]
501mod tests {
502    use super::*;
503    use serde::Deserialize;
504
505    #[derive(Deserialize, Debug, PartialEq)]
506    struct TestBare {
507        #[serde(deserialize_with = "deserialize_u64")]
508        balance: u64,
509        #[serde(deserialize_with = "deserialize_i64")]
510        timestamp: i64,
511    }
512
513    #[derive(Deserialize, Debug, PartialEq)]
514    struct TestOption {
515        #[serde(default, deserialize_with = "deserialize_option_u64")]
516        balance: Option<u64>,
517        #[serde(default, deserialize_with = "deserialize_option_i64")]
518        timestamp: Option<i64>,
519    }
520
521    #[derive(Deserialize, Debug, PartialEq)]
522    struct TestOptionOption {
523        #[serde(default, deserialize_with = "deserialize_option_option_u64")]
524        balance: Option<Option<u64>>,
525        #[serde(default, deserialize_with = "deserialize_option_option_i64")]
526        timestamp: Option<Option<i64>>,
527    }
528
529    #[derive(Deserialize, Debug, PartialEq)]
530    struct TestVec {
531        #[serde(default, deserialize_with = "deserialize_option_vec_u64")]
532        values: Option<Vec<u64>>,
533    }
534
535    #[derive(Deserialize, Debug, PartialEq)]
536    struct TestOptionOptionVec {
537        #[serde(default, deserialize_with = "deserialize_option_option_vec_u64")]
538        values: Option<Option<Vec<u64>>>,
539    }
540
541    #[derive(Deserialize, Debug, PartialEq)]
542    struct TestVecI64 {
543        #[serde(default, deserialize_with = "deserialize_option_vec_i64")]
544        values: Option<Vec<i64>>,
545    }
546
547    #[derive(Deserialize, Debug, PartialEq)]
548    struct TestOptionOptionVecI64 {
549        #[serde(default, deserialize_with = "deserialize_option_option_vec_i64")]
550        values: Option<Option<Vec<i64>>>,
551    }
552
553    // ── Bare types ──
554
555    #[test]
556    fn bare_u64_from_number() {
557        let v: TestBare = serde_json::from_str(r#"{"balance": 42, "timestamp": -100}"#).unwrap();
558        assert_eq!(v.balance, 42);
559        assert_eq!(v.timestamp, -100);
560    }
561
562    #[test]
563    fn bare_u64_from_string() {
564        let v: TestBare =
565            serde_json::from_str(r#"{"balance": "9007199254740992", "timestamp": "-100"}"#)
566                .unwrap();
567        assert_eq!(v.balance, 9007199254740992);
568        assert_eq!(v.timestamp, -100);
569    }
570
571    // ── Option<T> ──
572
573    #[test]
574    fn option_from_number() {
575        let v: TestOption = serde_json::from_str(r#"{"balance": 42, "timestamp": -100}"#).unwrap();
576        assert_eq!(v.balance, Some(42));
577        assert_eq!(v.timestamp, Some(-100));
578    }
579
580    #[test]
581    fn option_from_string() {
582        let v: TestOption =
583            serde_json::from_str(r#"{"balance": "9007199254740992", "timestamp": "123"}"#).unwrap();
584        assert_eq!(v.balance, Some(9007199254740992));
585        assert_eq!(v.timestamp, Some(123));
586    }
587
588    #[test]
589    fn option_from_null() {
590        let v: TestOption =
591            serde_json::from_str(r#"{"balance": null, "timestamp": null}"#).unwrap();
592        assert_eq!(v.balance, None);
593        assert_eq!(v.timestamp, None);
594    }
595
596    #[test]
597    fn option_missing_field() {
598        let v: TestOption = serde_json::from_str(r#"{}"#).unwrap();
599        assert_eq!(v.balance, None);
600        assert_eq!(v.timestamp, None);
601    }
602
603    // ── Option<Option<T>> ──
604
605    #[test]
606    fn option_option_from_number() {
607        let v: TestOptionOption =
608            serde_json::from_str(r#"{"balance": 42, "timestamp": -100}"#).unwrap();
609        assert_eq!(v.balance, Some(Some(42)));
610        assert_eq!(v.timestamp, Some(Some(-100)));
611    }
612
613    #[test]
614    fn option_option_from_string() {
615        let v: TestOptionOption =
616            serde_json::from_str(r#"{"balance": "9007199254740992", "timestamp": "123"}"#).unwrap();
617        assert_eq!(v.balance, Some(Some(9007199254740992)));
618        assert_eq!(v.timestamp, Some(Some(123)));
619    }
620
621    #[test]
622    fn option_option_null_means_explicit_null() {
623        let v: TestOptionOption =
624            serde_json::from_str(r#"{"balance": null, "timestamp": null}"#).unwrap();
625        assert_eq!(v.balance, Some(None)); // explicitly null
626        assert_eq!(v.timestamp, Some(None));
627    }
628
629    #[test]
630    fn option_option_missing_means_not_received() {
631        let v: TestOptionOption = serde_json::from_str(r#"{}"#).unwrap();
632        assert_eq!(v.balance, None); // not in patch
633        assert_eq!(v.timestamp, None);
634    }
635
636    // ── Vec variants ──
637
638    #[test]
639    fn vec_mixed_numbers_and_strings() {
640        let v: TestVec = serde_json::from_str(r#"{"values": [1, "9007199254740992", 3]}"#).unwrap();
641        assert_eq!(v.values, Some(vec![1, 9007199254740992, 3]));
642    }
643
644    #[test]
645    fn vec_null() {
646        let v: TestVec = serde_json::from_str(r#"{"values": null}"#).unwrap();
647        assert_eq!(v.values, None);
648    }
649
650    #[test]
651    fn vec_missing() {
652        let v: TestVec = serde_json::from_str(r#"{}"#).unwrap();
653        assert_eq!(v.values, None);
654    }
655
656    #[test]
657    fn option_option_vec_from_array() {
658        let v: TestOptionOptionVec =
659            serde_json::from_str(r#"{"values": [1, "9007199254740992"]}"#).unwrap();
660        assert_eq!(v.values, Some(Some(vec![1, 9007199254740992])));
661    }
662
663    #[test]
664    fn option_option_vec_null() {
665        let v: TestOptionOptionVec = serde_json::from_str(r#"{"values": null}"#).unwrap();
666        assert_eq!(v.values, Some(None));
667    }
668
669    #[test]
670    fn option_option_vec_missing() {
671        let v: TestOptionOptionVec = serde_json::from_str(r#"{}"#).unwrap();
672        assert_eq!(v.values, None);
673    }
674
675    // ── Vec<i64> variants ──
676
677    #[test]
678    fn vec_i64_mixed_numbers_and_strings() {
679        let v: TestVecI64 =
680            serde_json::from_str(r#"{"values": [-1, "9007199254740992", 3]}"#).unwrap();
681        assert_eq!(v.values, Some(vec![-1, 9007199254740992, 3]));
682    }
683
684    #[test]
685    fn vec_i64_null() {
686        let v: TestVecI64 = serde_json::from_str(r#"{"values": null}"#).unwrap();
687        assert_eq!(v.values, None);
688    }
689
690    #[test]
691    fn vec_i64_missing() {
692        let v: TestVecI64 = serde_json::from_str(r#"{}"#).unwrap();
693        assert_eq!(v.values, None);
694    }
695
696    #[test]
697    fn option_option_vec_i64_from_array() {
698        let v: TestOptionOptionVecI64 =
699            serde_json::from_str(r#"{"values": [-1, "9007199254740992"]}"#).unwrap();
700        assert_eq!(v.values, Some(Some(vec![-1, 9007199254740992])));
701    }
702
703    #[test]
704    fn option_option_vec_i64_null() {
705        let v: TestOptionOptionVecI64 = serde_json::from_str(r#"{"values": null}"#).unwrap();
706        assert_eq!(v.values, Some(None));
707    }
708
709    #[test]
710    fn option_option_vec_i64_missing() {
711        let v: TestOptionOptionVecI64 = serde_json::from_str(r#"{}"#).unwrap();
712        assert_eq!(v.values, None);
713    }
714
715    // ── Edge cases ──
716
717    #[test]
718    fn large_u64_from_string() {
719        let v: TestOption = serde_json::from_str(r#"{"balance": "18446744073709551615"}"#).unwrap();
720        assert_eq!(v.balance, Some(u64::MAX));
721    }
722
723    #[test]
724    fn u64_from_float() {
725        let v: TestOption = serde_json::from_str(r#"{"balance": 42.0}"#).unwrap();
726        assert_eq!(v.balance, Some(42));
727    }
728
729    // ── 32-bit narrowing ──
730
731    #[derive(Deserialize, Debug, PartialEq)]
732    struct TestOption32 {
733        #[serde(default, deserialize_with = "deserialize_option_u32")]
734        balance: Option<u32>,
735        #[serde(default, deserialize_with = "deserialize_option_i32")]
736        timestamp: Option<i32>,
737    }
738
739    #[test]
740    fn option_u32_from_number() {
741        let v: TestOption32 =
742            serde_json::from_str(r#"{"balance": 42, "timestamp": -100}"#).unwrap();
743        assert_eq!(v.balance, Some(42));
744        assert_eq!(v.timestamp, Some(-100));
745    }
746
747    #[test]
748    fn option_u32_from_string() {
749        let v: TestOption32 =
750            serde_json::from_str(r#"{"balance": "1000", "timestamp": "-50"}"#).unwrap();
751        assert_eq!(v.balance, Some(1000));
752        assert_eq!(v.timestamp, Some(-50));
753    }
754
755    #[test]
756    fn option_u32_overflow_rejected() {
757        let r = serde_json::from_str::<TestOption32>(r#"{"balance": 4294967296}"#);
758        assert!(r.is_err(), "u32 overflow should be rejected");
759    }
760
761    #[test]
762    fn option_i32_overflow_rejected() {
763        let r = serde_json::from_str::<TestOption32>(r#"{"timestamp": 2147483648}"#);
764        assert!(r.is_err(), "i32 overflow should be rejected");
765    }
766
767    #[test]
768    fn option_u32_null_and_missing() {
769        let v: TestOption32 =
770            serde_json::from_str(r#"{"balance": null, "timestamp": null}"#).unwrap();
771        assert_eq!(v.balance, None);
772        assert_eq!(v.timestamp, None);
773        let v: TestOption32 = serde_json::from_str(r#"{}"#).unwrap();
774        assert_eq!(v.balance, None);
775        assert_eq!(v.timestamp, None);
776    }
777
778    #[derive(Deserialize, Debug, PartialEq)]
779    struct TestOptionOption32 {
780        #[serde(default, deserialize_with = "deserialize_option_option_u32")]
781        balance: Option<Option<u32>>,
782    }
783
784    #[test]
785    fn option_option_u32_patch_semantics() {
786        let v: TestOptionOption32 = serde_json::from_str(r#"{"balance": 42}"#).unwrap();
787        assert_eq!(v.balance, Some(Some(42)));
788        let v: TestOptionOption32 = serde_json::from_str(r#"{"balance": null}"#).unwrap();
789        assert_eq!(v.balance, Some(None));
790        let v: TestOptionOption32 = serde_json::from_str(r#"{}"#).unwrap();
791        assert_eq!(v.balance, None);
792    }
793
794    #[derive(Deserialize, Debug, PartialEq)]
795    struct TestVec32 {
796        #[serde(default, deserialize_with = "deserialize_option_vec_u32")]
797        values: Option<Vec<u32>>,
798    }
799
800    #[test]
801    fn vec_u32_mixed() {
802        let v: TestVec32 = serde_json::from_str(r#"{"values": [1, "2", 3]}"#).unwrap();
803        assert_eq!(v.values, Some(vec![1, 2, 3]));
804    }
805
806    #[test]
807    fn vec_u32_overflow_rejected() {
808        let r = serde_json::from_str::<TestVec32>(r#"{"values": [1, 4294967296]}"#);
809        assert!(r.is_err());
810    }
811}