Skip to main content

nautilus_core/
serialization.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Common serialization traits and functions.
17//!
18//! This module provides custom serde deserializers and serializers for common
19//! patterns encountered when parsing exchange API responses, particularly:
20//!
21//! - Empty strings that should be interpreted as `None` or zero.
22//! - Type conversions from strings to primitives.
23//! - Decimal values represented as strings.
24
25use std::str::FromStr;
26
27use bytes::Bytes;
28use rust_decimal::Decimal;
29use serde::{
30    Deserialize, Deserializer, Serialize, Serializer,
31    de::{Error, Unexpected, Visitor},
32    ser::SerializeSeq,
33};
34use ustr::Ustr;
35
36/// Sorted serialization for `AHashSet<T>` where element order must be deterministic.
37///
38/// Use with `#[serde(with = "nautilus_core::serialization::sorted_hashset")]`.
39pub mod sorted_hashset {
40    use ahash::AHashSet;
41    use serde::{Deserialize, Deserializer, Serialize, Serializer};
42
43    /// Serializes an `AHashSet<T>` as a sorted array for deterministic output.
44    ///
45    /// # Errors
46    ///
47    /// Returns any error produced by the underlying [`Serializer`] when writing
48    /// the sorted vector.
49    pub fn serialize<T, S>(set: &AHashSet<T>, s: S) -> Result<S::Ok, S::Error>
50    where
51        T: Serialize + Ord,
52        S: Serializer,
53    {
54        let mut sorted: Vec<&T> = set.iter().collect();
55        sorted.sort_unstable();
56        sorted.serialize(s)
57    }
58
59    /// Deserializes an array into an `AHashSet<T>`.
60    ///
61    /// # Errors
62    ///
63    /// Returns any error produced by the underlying [`Deserializer`] when reading
64    /// the source array.
65    pub fn deserialize<'de, T, D>(d: D) -> Result<AHashSet<T>, D::Error>
66    where
67        T: Deserialize<'de> + Eq + std::hash::Hash,
68        D: Deserializer<'de>,
69    {
70        let vec = Vec::<T>::deserialize(d)?;
71        Ok(vec.into_iter().collect())
72    }
73}
74
75/// Zero-allocation decimal visitor for maximum deserialization performance.
76///
77/// Directly visits JSON tokens without intermediate `serde_json::Value` allocation.
78/// Handles all JSON numeric representations: strings, integers, floats, and null.
79struct DecimalVisitor;
80
81impl Visitor<'_> for DecimalVisitor {
82    type Value = Decimal;
83
84    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
85        formatter.write_str("a decimal number as string, integer, or float")
86    }
87
88    // Fast path: borrowed string (zero-copy)
89    fn visit_str<E: Error>(self, v: &str) -> Result<Self::Value, E> {
90        if v.is_empty() {
91            return Ok(Decimal::ZERO);
92        }
93        parse_decimal_str(v).map_err(E::custom)
94    }
95
96    // Owned string (rare case, delegates to visit_str)
97    fn visit_string<E: Error>(self, v: String) -> Result<Self::Value, E> {
98        self.visit_str(&v)
99    }
100
101    // Direct integer handling - no string conversion needed
102    fn visit_i64<E: Error>(self, v: i64) -> Result<Self::Value, E> {
103        Ok(Decimal::from(v))
104    }
105
106    fn visit_u64<E: Error>(self, v: u64) -> Result<Self::Value, E> {
107        Ok(Decimal::from(v))
108    }
109
110    fn visit_i128<E: Error>(self, v: i128) -> Result<Self::Value, E> {
111        Ok(Decimal::from(v))
112    }
113
114    fn visit_u128<E: Error>(self, v: u128) -> Result<Self::Value, E> {
115        Ok(Decimal::from(v))
116    }
117
118    // Float handling - direct conversion
119    fn visit_f64<E: Error>(self, v: f64) -> Result<Self::Value, E> {
120        if !v.is_finite() {
121            return Err(E::invalid_value(Unexpected::Float(v), &self));
122        }
123        Decimal::try_from(v).map_err(E::custom)
124    }
125
126    // Null → zero (matches existing behavior)
127    fn visit_unit<E: Error>(self) -> Result<Self::Value, E> {
128        Ok(Decimal::ZERO)
129    }
130
131    fn visit_none<E: Error>(self) -> Result<Self::Value, E> {
132        Ok(Decimal::ZERO)
133    }
134}
135
136/// Zero-allocation optional decimal visitor for maximum deserialization performance.
137///
138/// Handles null values as `None` and empty strings as `None`.
139/// Uses `deserialize_any` approach to handle all JSON value types uniformly.
140struct OptionalDecimalVisitor;
141
142impl Visitor<'_> for OptionalDecimalVisitor {
143    type Value = Option<Decimal>;
144
145    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
146        formatter.write_str("null or a decimal number as string, integer, or float")
147    }
148
149    // Fast path: borrowed string (zero-copy)
150    // Empty string → None (different from DecimalVisitor which returns ZERO)
151    fn visit_str<E: Error>(self, v: &str) -> Result<Self::Value, E> {
152        if v.is_empty() {
153            return Ok(None);
154        }
155        DecimalVisitor.visit_str(v).map(Some)
156    }
157
158    fn visit_string<E: Error>(self, v: String) -> Result<Self::Value, E> {
159        self.visit_str(&v)
160    }
161
162    fn visit_i64<E: Error>(self, v: i64) -> Result<Self::Value, E> {
163        DecimalVisitor.visit_i64(v).map(Some)
164    }
165
166    fn visit_u64<E: Error>(self, v: u64) -> Result<Self::Value, E> {
167        DecimalVisitor.visit_u64(v).map(Some)
168    }
169
170    fn visit_i128<E: Error>(self, v: i128) -> Result<Self::Value, E> {
171        DecimalVisitor.visit_i128(v).map(Some)
172    }
173
174    fn visit_u128<E: Error>(self, v: u128) -> Result<Self::Value, E> {
175        DecimalVisitor.visit_u128(v).map(Some)
176    }
177
178    fn visit_f64<E: Error>(self, v: f64) -> Result<Self::Value, E> {
179        DecimalVisitor.visit_f64(v).map(Some)
180    }
181
182    // Null → None
183    fn visit_unit<E: Error>(self) -> Result<Self::Value, E> {
184        Ok(None)
185    }
186
187    fn visit_none<E: Error>(self) -> Result<Self::Value, E> {
188        Ok(None)
189    }
190}
191
192fn parse_decimal_str(value: &str) -> Result<Decimal, String> {
193    let parsed = if value.contains('e') || value.contains('E') {
194        Decimal::from_scientific(value)
195    } else {
196        Decimal::from_str(value)
197    };
198
199    match parsed {
200        Ok(decimal) => Ok(decimal),
201        Err(e) => {
202            // Fractional digits beyond Decimal's maximum scale are
203            // sub-representable; round to the highest scale that fits
204            // (venues quoting 18-decimal on-chain units emit such values).
205            for scale in (0..=Decimal::MAX_SCALE as usize).rev() {
206                let clamped =
207                    decimal_string_clamped_to_scale(value, scale).ok_or_else(|| e.to_string())?;
208
209                if let Ok(decimal) = Decimal::from_str(&clamped) {
210                    return Ok(decimal);
211                }
212            }
213            Err(e.to_string())
214        }
215    }
216}
217
218fn decimal_string_clamped_to_scale(value: &str, max_scale: usize) -> Option<String> {
219    let (coefficient, exponent) = match value.find(['e', 'E']) {
220        Some(index) => {
221            let exponent = value[index + 1..].parse::<i32>().ok()?;
222            (&value[..index], exponent)
223        }
224        None => (value, 0),
225    };
226
227    let (sign, unsigned) = match coefficient.as_bytes().first()? {
228        b'+' => ("", &coefficient[1..]),
229        b'-' => ("-", &coefficient[1..]),
230        _ => ("", coefficient),
231    };
232    let (integer, fractional) = decimal_components(unsigned)?;
233    let digits = format!("{integer}{fractional}");
234    if decimal_digits_are_zero(&digits, "") {
235        return Some("0".to_string());
236    }
237
238    let point = i32::try_from(integer.len()).ok()?.checked_add(exponent)?;
239
240    // Decimal can hold at most 29 significant integer digits, and
241    // `Decimal::from_str` below still rejects values above Decimal::MAX.
242    // Check significant digits before expansion so absurd exponents keep the
243    // original parse error without allocating an absurd string.
244    if decimal_integer_digits_exceed_max(&digits, point) {
245        return None;
246    }
247
248    let (integer, fractional) = if point <= 0 {
249        // Digits past the rounding position are leading zeros, so the cap
250        // cannot change the result; bounds allocation for absurd exponents.
251        let zero_count = usize::try_from(-point).ok()?.min(max_scale + 1);
252        (
253            "0".to_string(),
254            format!("{}{digits}", "0".repeat(zero_count)),
255        )
256    } else {
257        let point = usize::try_from(point).ok()?;
258        if point >= digits.len() {
259            (
260                format!("{}{}", digits, "0".repeat(point - digits.len())),
261                String::new(),
262            )
263        } else {
264            (digits[..point].to_string(), digits[point..].to_string())
265        }
266    };
267
268    let (integer, fractional) = round_decimal_components(integer, fractional, max_scale);
269    let sign = if sign == "-" && decimal_digits_are_zero(&integer, &fractional) {
270        ""
271    } else {
272        sign
273    };
274
275    if fractional.is_empty() {
276        Some(format!("{sign}{integer}"))
277    } else {
278        Some(format!("{sign}{integer}.{fractional}"))
279    }
280}
281
282fn decimal_components(value: &str) -> Option<(&str, &str)> {
283    let mut split = value.split('.');
284    let integer = split.next()?;
285    let fractional = split.next().unwrap_or("");
286    if split.next().is_some()
287        || (integer.is_empty() && fractional.is_empty())
288        || !integer.chars().all(|c| c.is_ascii_digit())
289        || !fractional.chars().all(|c| c.is_ascii_digit())
290    {
291        return None;
292    }
293    Some((integer, fractional))
294}
295
296fn decimal_integer_digits_exceed_max(digits: &str, point: i32) -> bool {
297    const DECIMAL_MAX_INTEGER_DIGITS: i32 = 29;
298
299    if point <= 0 {
300        return false;
301    }
302
303    let Some(first_non_zero) = digits.bytes().position(|digit| digit != b'0') else {
304        return false;
305    };
306    let Ok(first_non_zero) = i32::try_from(first_non_zero) else {
307        return true;
308    };
309
310    point.saturating_sub(first_non_zero) > DECIMAL_MAX_INTEGER_DIGITS
311}
312
313fn round_decimal_components(
314    mut integer: String,
315    fractional: String,
316    max_scale: usize,
317) -> (String, String) {
318    if fractional.len() <= max_scale {
319        return (integer, fractional);
320    }
321
322    let mut rounded = fractional.as_bytes()[..max_scale].to_vec();
323    if fractional.as_bytes()[max_scale] >= b'5' {
324        increment_decimal_digits(&mut integer, &mut rounded);
325    }
326
327    (
328        integer,
329        String::from_utf8(rounded).expect("decimal digits are ASCII"),
330    )
331}
332
333fn increment_decimal_digits(integer: &mut String, fractional: &mut [u8]) {
334    for digit in fractional.iter_mut().rev() {
335        if *digit < b'9' {
336            *digit += 1;
337            return;
338        }
339        *digit = b'0';
340    }
341
342    let mut integer_digits = integer.as_bytes().to_vec();
343    for digit in integer_digits.iter_mut().rev() {
344        if *digit < b'9' {
345            *digit += 1;
346            *integer = String::from_utf8(integer_digits).expect("decimal digits are ASCII");
347            return;
348        }
349        *digit = b'0';
350    }
351    integer_digits.insert(0, b'1');
352    *integer = String::from_utf8(integer_digits).expect("decimal digits are ASCII");
353}
354
355fn decimal_digits_are_zero(integer: &str, fractional: &str) -> bool {
356    integer
357        .bytes()
358        .chain(fractional.bytes())
359        .all(|digit| digit == b'0')
360}
361
362/// Represents types which are serializable for JSON specifications.
363pub trait Serializable: Serialize + for<'de> Deserialize<'de> {
364    /// Deserialize an object from JSON encoded bytes.
365    ///
366    /// # Errors
367    ///
368    /// Returns serialization errors.
369    fn from_json_bytes(data: &[u8]) -> Result<Self, serde_json::Error> {
370        serde_json::from_slice(data)
371    }
372
373    /// Serialize an object to JSON encoded bytes.
374    ///
375    /// # Errors
376    ///
377    /// Returns serialization errors.
378    fn to_json_bytes(&self) -> Result<Bytes, serde_json::Error> {
379        serde_json::to_vec(self).map(Bytes::from)
380    }
381}
382
383pub use self::msgpack::{FromMsgPack, MsgPackSerializable, ToMsgPack};
384
385/// Provides `MsgPack` serialization support for types implementing [`Serializable`].
386///
387/// This module contains traits for `MsgPack` serialization and deserialization,
388/// separated from the core [`Serializable`] trait to allow independent opt-in.
389pub mod msgpack {
390    use bytes::Bytes;
391    use serde::{Deserialize, Serialize};
392
393    use super::Serializable;
394
395    /// Provides deserialization from `MsgPack` encoded bytes.
396    pub trait FromMsgPack: for<'de> Deserialize<'de> + Sized {
397        /// Deserialize an object from `MsgPack` encoded bytes.
398        ///
399        /// # Errors
400        ///
401        /// Returns serialization errors.
402        fn from_msgpack_bytes(data: &[u8]) -> Result<Self, rmp_serde::decode::Error> {
403            rmp_serde::from_slice(data)
404        }
405    }
406
407    /// Provides serialization to `MsgPack` encoded bytes.
408    pub trait ToMsgPack: Serialize {
409        /// Serialize an object to `MsgPack` encoded bytes.
410        ///
411        /// # Errors
412        ///
413        /// Returns serialization errors.
414        fn to_msgpack_bytes(&self) -> Result<Bytes, rmp_serde::encode::Error> {
415            rmp_serde::to_vec_named(self).map(Bytes::from)
416        }
417    }
418
419    /// Marker trait combining [`Serializable`], [`FromMsgPack`], and [`ToMsgPack`].
420    ///
421    /// This trait is automatically implemented for all types that implement [`Serializable`].
422    pub trait MsgPackSerializable: Serializable + FromMsgPack + ToMsgPack {}
423
424    impl<T> FromMsgPack for T where T: Serializable {}
425
426    impl<T> ToMsgPack for T where T: Serializable {}
427
428    impl<T> MsgPackSerializable for T where T: Serializable {}
429}
430
431/// Serde default value function that returns `true`.
432///
433/// Use with `#[serde(default = "default_true")]` on boolean fields.
434#[must_use]
435pub const fn default_true() -> bool {
436    true
437}
438
439/// Serde default value function that returns `false`.
440///
441/// Use with `#[serde(default = "default_false")]` on boolean fields.
442#[must_use]
443pub const fn default_false() -> bool {
444    false
445}
446
447/// Deserializes a `Decimal` from either a JSON string or number.
448///
449/// High-performance implementation using a custom visitor that avoids intermediate
450/// `serde_json::Value` allocations. Handles all JSON numeric representations:
451///
452/// - JSON string: `"123.456"` → Decimal (zero-copy for borrowed strings)
453/// - JSON integer: `123` → Decimal (direct conversion, no string allocation)
454/// - JSON float: `123.456` → Decimal
455/// - JSON null: → `Decimal::ZERO`
456/// - Scientific notation: `"1.5e-8"` → Decimal
457/// - Fractional digits beyond `Decimal`'s maximum scale (28) are rounded
458///
459/// # Performance
460///
461/// This implementation is optimized for high-frequency trading scenarios:
462/// - Zero allocations for string values (uses borrowed `&str`)
463/// - Direct integer conversion without string intermediary
464/// - No intermediate `serde_json::Value` heap allocation
465///
466/// # Errors
467///
468/// Returns an error if the value cannot be parsed as a valid decimal.
469pub fn deserialize_decimal<'de, D>(deserializer: D) -> Result<Decimal, D::Error>
470where
471    D: Deserializer<'de>,
472{
473    deserializer.deserialize_any(DecimalVisitor)
474}
475
476/// Deserializes an `Option<Decimal>` from a JSON string, number, or null.
477///
478/// High-performance implementation using a custom visitor that avoids intermediate
479/// `serde_json::Value` allocations. Handles all JSON numeric representations:
480///
481/// - JSON string: `"123.456"` → Some(Decimal) (zero-copy for borrowed strings)
482/// - JSON integer: `123` → Some(Decimal) (direct conversion)
483/// - JSON float: `123.456` → Some(Decimal)
484/// - JSON null: → `None`
485/// - Empty string: `""` → `None`
486/// - Scientific notation: `"1.5e-8"` → Some(Decimal)
487/// - Fractional digits beyond `Decimal`'s maximum scale (28) are rounded
488///
489/// # Performance
490///
491/// This implementation is optimized for high-frequency trading scenarios:
492/// - Zero allocations for string values (uses borrowed `&str`)
493/// - Direct integer conversion without string intermediary
494/// - No intermediate `serde_json::Value` heap allocation
495///
496/// # Errors
497///
498/// Returns an error if the value cannot be parsed as a valid decimal.
499pub fn deserialize_optional_decimal<'de, D>(deserializer: D) -> Result<Option<Decimal>, D::Error>
500where
501    D: Deserializer<'de>,
502{
503    // Use deserialize_any to handle all JSON value types uniformly
504    // (deserialize_option would route non-null through visit_some, losing empty string handling)
505    deserializer.deserialize_any(OptionalDecimalVisitor)
506}
507
508/// Serializes a `Decimal` as a JSON number (float).
509///
510/// Used for outgoing requests where exchange APIs expect JSON numbers.
511///
512/// # Errors
513///
514/// Returns an error if serialization fails.
515pub fn serialize_decimal<S: Serializer>(d: &Decimal, s: S) -> Result<S::Ok, S::Error> {
516    rust_decimal::serde::float::serialize(d, s)
517}
518
519/// Serializes an `Option<Decimal>` as a JSON number or null.
520///
521/// # Errors
522///
523/// Returns an error if serialization fails.
524pub fn serialize_optional_decimal<S: Serializer>(
525    d: &Option<Decimal>,
526    s: S,
527) -> Result<S::Ok, S::Error> {
528    match d {
529        Some(decimal) => rust_decimal::serde::float::serialize(decimal, s),
530        None => s.serialize_none(),
531    }
532}
533
534/// Deserializes a `Decimal` from a JSON string.
535///
536/// This is the strict form that requires the value to be a string, rejecting
537/// numeric JSON values to avoid precision loss.
538///
539/// # Errors
540///
541/// Returns an error if the string cannot be parsed as a valid decimal.
542pub fn deserialize_decimal_from_str<'de, D>(deserializer: D) -> Result<Decimal, D::Error>
543where
544    D: Deserializer<'de>,
545{
546    let s: std::borrow::Cow<'de, str> = Deserialize::deserialize(deserializer)?;
547    Decimal::from_str(s.as_ref()).map_err(D::Error::custom)
548}
549
550/// Deserializes a `Decimal` from a string field that might be empty.
551///
552/// Handles edge cases where empty string "" or "0" becomes `Decimal::ZERO`.
553///
554/// # Errors
555///
556/// Returns an error if the string cannot be parsed as a valid decimal.
557pub fn deserialize_decimal_or_zero<'de, D>(deserializer: D) -> Result<Decimal, D::Error>
558where
559    D: Deserializer<'de>,
560{
561    let s: std::borrow::Cow<'de, str> = Deserialize::deserialize(deserializer)?;
562    if s.is_empty() || s == "0" {
563        Ok(Decimal::ZERO)
564    } else {
565        Decimal::from_str(s.as_ref()).map_err(D::Error::custom)
566    }
567}
568
569/// Deserializes an optional `Decimal` from a string field.
570///
571/// Returns `None` if the string is empty or "0", otherwise parses to `Decimal`.
572/// This is a strict string-only deserializer; for flexible handling of strings,
573/// numbers, and null, use [`deserialize_optional_decimal`].
574///
575/// # Errors
576///
577/// Returns an error if the string cannot be parsed as a valid decimal.
578pub fn deserialize_optional_decimal_str<'de, D>(
579    deserializer: D,
580) -> Result<Option<Decimal>, D::Error>
581where
582    D: Deserializer<'de>,
583{
584    let s: std::borrow::Cow<'de, str> = Deserialize::deserialize(deserializer)?;
585    if s.is_empty() || s == "0" {
586        Ok(None)
587    } else {
588        Decimal::from_str(s.as_ref())
589            .map(Some)
590            .map_err(D::Error::custom)
591    }
592}
593
594/// Deserializes an optional `Decimal` from a string-only field.
595///
596/// Returns `None` if the value is null or the string is empty, otherwise
597/// parses to `Decimal`.
598///
599/// # Errors
600///
601/// Returns an error if the string cannot be parsed as a valid decimal.
602pub fn deserialize_optional_decimal_from_str<'de, D>(
603    deserializer: D,
604) -> Result<Option<Decimal>, D::Error>
605where
606    D: Deserializer<'de>,
607{
608    let opt = Option::<String>::deserialize(deserializer)?;
609    match opt {
610        Some(s) if !s.is_empty() => Decimal::from_str(&s).map(Some).map_err(D::Error::custom),
611        _ => Ok(None),
612    }
613}
614
615/// Deserializes a `Decimal` from an optional string field, defaulting to zero.
616///
617/// Handles edge cases: `None`, empty string "", or "0" all become `Decimal::ZERO`.
618///
619/// # Errors
620///
621/// Returns an error if the string cannot be parsed as a valid decimal.
622pub fn deserialize_optional_decimal_or_zero<'de, D>(deserializer: D) -> Result<Decimal, D::Error>
623where
624    D: Deserializer<'de>,
625{
626    let opt: Option<String> = Deserialize::deserialize(deserializer)?;
627    match opt {
628        None => Ok(Decimal::ZERO),
629        Some(s) if s.is_empty() || s == "0" => Ok(Decimal::ZERO),
630        Some(s) => Decimal::from_str(&s).map_err(D::Error::custom),
631    }
632}
633
634/// Deserializes a `Vec<Decimal>` from a JSON array of strings.
635///
636/// # Errors
637///
638/// Returns an error if any string cannot be parsed as a valid decimal.
639pub fn deserialize_vec_decimal_from_str<'de, D>(deserializer: D) -> Result<Vec<Decimal>, D::Error>
640where
641    D: Deserializer<'de>,
642{
643    let strings = Vec::<String>::deserialize(deserializer)?;
644    strings
645        .into_iter()
646        .map(|s| Decimal::from_str(&s).map_err(D::Error::custom))
647        .collect()
648}
649
650/// Serializes a `Decimal` as a string (lossless, no scientific notation).
651///
652/// # Errors
653///
654/// Returns an error if serialization fails.
655pub fn serialize_decimal_as_str<S>(decimal: &Decimal, serializer: S) -> Result<S::Ok, S::Error>
656where
657    S: Serializer,
658{
659    serializer.serialize_str(&decimal.to_string())
660}
661
662/// Serializes an optional `Decimal` as a string.
663///
664/// # Errors
665///
666/// Returns an error if serialization fails.
667pub fn serialize_optional_decimal_as_str<S>(
668    decimal: &Option<Decimal>,
669    serializer: S,
670) -> Result<S::Ok, S::Error>
671where
672    S: Serializer,
673{
674    match decimal {
675        Some(d) => serializer.serialize_str(&d.to_string()),
676        None => serializer.serialize_none(),
677    }
678}
679
680/// Serializes a `Vec<Decimal>` as an array of strings.
681///
682/// # Errors
683///
684/// Returns an error if serialization fails.
685pub fn serialize_vec_decimal_as_str<S>(
686    decimals: &Vec<Decimal>,
687    serializer: S,
688) -> Result<S::Ok, S::Error>
689where
690    S: Serializer,
691{
692    let mut seq = serializer.serialize_seq(Some(decimals.len()))?;
693    for decimal in decimals {
694        seq.serialize_element(&decimal.to_string())?;
695    }
696    seq.end()
697}
698
699/// Parses a string to `Decimal`, returning an error if parsing fails.
700///
701/// # Errors
702///
703/// Returns an error if the string cannot be parsed as a Decimal.
704pub fn parse_decimal(s: &str) -> anyhow::Result<Decimal> {
705    Decimal::from_str(s).map_err(|e| anyhow::anyhow!("Failed to parse decimal from '{s}': {e}"))
706}
707
708/// Parses an optional string to `Decimal`, returning `None` if the string is `None` or empty.
709///
710/// # Errors
711///
712/// Returns an error if the string cannot be parsed as a Decimal.
713pub fn parse_optional_decimal(s: &Option<String>) -> anyhow::Result<Option<Decimal>> {
714    match s {
715        None => Ok(None),
716        Some(s) if s.is_empty() => Ok(None),
717        Some(s) => parse_decimal(s).map(Some),
718    }
719}
720
721/// Deserializes an empty string into `None`.
722///
723/// Many exchange APIs represent null string fields as an empty string (`""`).
724/// When such a payload is mapped onto `Option<String>` the default behavior
725/// would yield `Some("")`, which is semantically different from the intended
726/// absence of a value. This deserializer normalizes empty strings
727/// to `None` during deserialization.
728///
729/// # Errors
730///
731/// Returns an error if the JSON value cannot be deserialized into a string.
732pub fn deserialize_empty_string_as_none<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
733where
734    D: Deserializer<'de>,
735{
736    let opt = Option::<String>::deserialize(deserializer)?;
737    Ok(opt.filter(|s| !s.is_empty()))
738}
739
740/// Deserializes an empty [`Ustr`] into `None`.
741///
742/// # Errors
743///
744/// Returns an error if the JSON value cannot be deserialized into a string.
745pub fn deserialize_empty_ustr_as_none<'de, D>(deserializer: D) -> Result<Option<Ustr>, D::Error>
746where
747    D: Deserializer<'de>,
748{
749    let opt = Option::<Ustr>::deserialize(deserializer)?;
750    Ok(opt.filter(|s| !s.is_empty()))
751}
752
753/// Deserializes a `u8` from a string field.
754///
755/// Returns 0 if the string is empty.
756///
757/// # Errors
758///
759/// Returns an error if the string cannot be parsed as a u8.
760pub fn deserialize_string_to_u8<'de, D>(deserializer: D) -> Result<u8, D::Error>
761where
762    D: Deserializer<'de>,
763{
764    let s: std::borrow::Cow<'de, str> = Deserialize::deserialize(deserializer)?;
765    if s.is_empty() {
766        return Ok(0);
767    }
768    s.as_ref().parse::<u8>().map_err(D::Error::custom)
769}
770
771/// Deserializes a `u64` from a string field.
772///
773/// Returns 0 if the string is empty.
774///
775/// # Errors
776///
777/// Returns an error if the string cannot be parsed as a u64.
778pub fn deserialize_string_to_u64<'de, D>(deserializer: D) -> Result<u64, D::Error>
779where
780    D: Deserializer<'de>,
781{
782    let s: std::borrow::Cow<'de, str> = Deserialize::deserialize(deserializer)?;
783    if s.is_empty() {
784        Ok(0)
785    } else {
786        s.as_ref().parse::<u64>().map_err(D::Error::custom)
787    }
788}
789
790/// Deserializes an optional `u64` from a string field.
791///
792/// Returns `None` if the value is null or the string is empty.
793///
794/// # Errors
795///
796/// Returns an error if the string cannot be parsed as a u64.
797pub fn deserialize_optional_string_to_u64<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
798where
799    D: Deserializer<'de>,
800{
801    let s: Option<String> = Option::deserialize(deserializer)?;
802    match s {
803        Some(s) if s.is_empty() => Ok(None),
804        Some(s) => s.parse().map(Some).map_err(D::Error::custom),
805        None => Ok(None),
806    }
807}
808
809#[cfg(test)]
810mod tests {
811    use ahash::AHashSet;
812    use rstest::*;
813    use rust_decimal::Decimal;
814    use rust_decimal_macros::dec;
815    use serde::{
816        Deserialize, Serialize,
817        de::{Visitor, value::Error as ValueError},
818    };
819    use serde_json::json;
820    use ustr::Ustr;
821
822    use super::{
823        DecimalVisitor, OptionalDecimalVisitor, Serializable, default_false, default_true,
824        deserialize_decimal, deserialize_decimal_from_str, deserialize_decimal_or_zero,
825        deserialize_empty_string_as_none, deserialize_empty_ustr_as_none,
826        deserialize_optional_decimal, deserialize_optional_decimal_or_zero,
827        deserialize_optional_decimal_str, deserialize_optional_string_to_u64,
828        deserialize_string_to_u8, deserialize_string_to_u64, deserialize_vec_decimal_from_str,
829        msgpack::{FromMsgPack, ToMsgPack},
830        parse_decimal, parse_optional_decimal, serialize_decimal, serialize_decimal_as_str,
831        serialize_optional_decimal, serialize_optional_decimal_as_str,
832        serialize_vec_decimal_as_str, sorted_hashset,
833    };
834
835    #[derive(Debug, PartialEq, Serialize, Deserialize)]
836    struct SortedSetPayload {
837        #[serde(with = "sorted_hashset")]
838        values: AHashSet<i32>,
839    }
840
841    #[derive(Serialize, Deserialize, PartialEq, Debug)]
842    struct SerializableTestStruct {
843        id: u32,
844        name: String,
845        value: f64,
846    }
847
848    impl Serializable for SerializableTestStruct {}
849
850    #[rstest]
851    fn test_sorted_hashset_serialization_is_deterministic() {
852        let payload = SortedSetPayload {
853            values: [29, 3, 17, 5, 23, 11].into_iter().collect(),
854        };
855
856        let encoded = serde_json::to_string(&payload).unwrap();
857        let expected = serde_json::to_string(&json!({ "values": [3, 5, 11, 17, 23, 29] })).unwrap();
858        let restored: SortedSetPayload = serde_json::from_str(&encoded).unwrap();
859
860        assert_eq!(encoded, expected);
861        assert_eq!(restored, payload);
862    }
863
864    #[rstest]
865    fn test_serializable_json_roundtrip() {
866        let original = SerializableTestStruct {
867            id: 42,
868            name: "test".to_string(),
869            value: std::f64::consts::PI,
870        };
871
872        let json_bytes = original.to_json_bytes().unwrap();
873        let deserialized = SerializableTestStruct::from_json_bytes(&json_bytes).unwrap();
874
875        assert_eq!(original, deserialized);
876    }
877
878    #[rstest]
879    fn test_serializable_msgpack_roundtrip() {
880        let original = SerializableTestStruct {
881            id: 123,
882            name: "msgpack_test".to_string(),
883            value: std::f64::consts::E,
884        };
885
886        let msgpack_bytes = original.to_msgpack_bytes().unwrap();
887        let deserialized = SerializableTestStruct::from_msgpack_bytes(&msgpack_bytes).unwrap();
888
889        assert_eq!(original, deserialized);
890    }
891
892    #[rstest]
893    fn test_serializable_json_invalid_data() {
894        let invalid_json = b"invalid json data";
895        let result = SerializableTestStruct::from_json_bytes(invalid_json);
896        assert!(result.is_err());
897    }
898
899    #[rstest]
900    fn test_serializable_msgpack_invalid_data() {
901        let invalid_msgpack = b"invalid msgpack data";
902        let result = SerializableTestStruct::from_msgpack_bytes(invalid_msgpack);
903        assert!(result.is_err());
904    }
905
906    #[rstest]
907    fn test_serializable_json_empty_values() {
908        let test_struct = SerializableTestStruct {
909            id: 0,
910            name: String::new(),
911            value: 0.0,
912        };
913
914        let json_bytes = test_struct.to_json_bytes().unwrap();
915        let deserialized = SerializableTestStruct::from_json_bytes(&json_bytes).unwrap();
916
917        assert_eq!(test_struct, deserialized);
918    }
919
920    #[rstest]
921    fn test_serializable_msgpack_empty_values() {
922        let test_struct = SerializableTestStruct {
923            id: 0,
924            name: String::new(),
925            value: 0.0,
926        };
927
928        let msgpack_bytes = test_struct.to_msgpack_bytes().unwrap();
929        let deserialized = SerializableTestStruct::from_msgpack_bytes(&msgpack_bytes).unwrap();
930
931        assert_eq!(test_struct, deserialized);
932    }
933
934    #[derive(Deserialize)]
935    struct TestOptionalDecimalStr {
936        #[serde(deserialize_with = "deserialize_optional_decimal_str")]
937        value: Option<Decimal>,
938    }
939
940    #[derive(Deserialize)]
941    struct TestDecimalOrZero {
942        #[serde(deserialize_with = "deserialize_decimal_or_zero")]
943        value: Decimal,
944    }
945
946    #[derive(Deserialize)]
947    struct TestOptionalDecimalOrZero {
948        #[serde(deserialize_with = "deserialize_optional_decimal_or_zero")]
949        value: Decimal,
950    }
951
952    #[derive(Serialize, Deserialize, PartialEq, Debug)]
953    struct TestDecimalRoundtrip {
954        #[serde(
955            serialize_with = "serialize_decimal_as_str",
956            deserialize_with = "deserialize_decimal_from_str"
957        )]
958        value: Decimal,
959        #[serde(
960            serialize_with = "serialize_optional_decimal_as_str",
961            deserialize_with = "super::deserialize_optional_decimal_from_str"
962        )]
963        optional_value: Option<Decimal>,
964    }
965
966    #[rstest]
967    #[case(r#"{"value":"123.45"}"#, Some(dec!(123.45)))]
968    #[case(r#"{"value":"0"}"#, None)]
969    #[case(r#"{"value":""}"#, None)]
970    fn test_deserialize_optional_decimal_str(
971        #[case] json: &str,
972        #[case] expected: Option<Decimal>,
973    ) {
974        let result: TestOptionalDecimalStr = serde_json::from_str(json).unwrap();
975        assert_eq!(result.value, expected);
976    }
977
978    #[rstest]
979    fn test_deserialize_optional_decimal_from_str_empty_is_none() {
980        let json = r#"{"value": "1.5", "optional_value": ""}"#;
981        let result: TestDecimalRoundtrip = serde_json::from_str(json).unwrap();
982        assert_eq!(result.optional_value, None);
983    }
984
985    #[rstest]
986    #[case(r#"{"value":"123.45"}"#, dec!(123.45))]
987    #[case(r#"{"value":"0"}"#, Decimal::ZERO)]
988    #[case(r#"{"value":""}"#, Decimal::ZERO)]
989    fn test_deserialize_decimal_or_zero(#[case] json: &str, #[case] expected: Decimal) {
990        let result: TestDecimalOrZero = serde_json::from_str(json).unwrap();
991        assert_eq!(result.value, expected);
992    }
993
994    #[rstest]
995    #[case(r#"{"value":"123.45"}"#, dec!(123.45))]
996    #[case(r#"{"value":"0"}"#, Decimal::ZERO)]
997    #[case(r#"{"value":null}"#, Decimal::ZERO)]
998    #[case(r#"{"value":""}"#, Decimal::ZERO)]
999    fn test_deserialize_optional_decimal_or_zero(#[case] json: &str, #[case] expected: Decimal) {
1000        let result: TestOptionalDecimalOrZero = serde_json::from_str(json).unwrap();
1001        assert_eq!(result.value, expected);
1002    }
1003
1004    #[rstest]
1005    fn test_decimal_serialization_roundtrip() {
1006        let original = TestDecimalRoundtrip {
1007            value: dec!(123.456789012345678),
1008            optional_value: Some(dec!(0.000000001)),
1009        };
1010
1011        let json = serde_json::to_string(&original).unwrap();
1012
1013        // Check that it's serialized as strings
1014        assert!(json.contains("\"123.456789012345678\""));
1015        assert!(json.contains("\"0.000000001\""));
1016
1017        let deserialized: TestDecimalRoundtrip = serde_json::from_str(&json).unwrap();
1018        assert_eq!(original.value, deserialized.value);
1019        assert_eq!(original.optional_value, deserialized.optional_value);
1020    }
1021
1022    #[rstest]
1023    fn test_decimal_optional_none_handling() {
1024        let test_struct = TestDecimalRoundtrip {
1025            value: dec!(42.0),
1026            optional_value: None,
1027        };
1028
1029        let json = serde_json::to_string(&test_struct).unwrap();
1030        assert!(json.contains("null"));
1031
1032        let parsed: TestDecimalRoundtrip = serde_json::from_str(&json).unwrap();
1033        assert_eq!(test_struct.value, parsed.value);
1034        assert_eq!(None, parsed.optional_value);
1035    }
1036
1037    #[derive(Deserialize)]
1038    struct TestEmptyStringAsNone {
1039        #[serde(deserialize_with = "deserialize_empty_string_as_none")]
1040        value: Option<String>,
1041    }
1042
1043    #[rstest]
1044    #[case(r#"{"value":"hello"}"#, Some("hello".to_string()))]
1045    #[case(r#"{"value":""}"#, None)]
1046    #[case(r#"{"value":null}"#, None)]
1047    fn test_deserialize_empty_string_as_none(#[case] json: &str, #[case] expected: Option<String>) {
1048        let result: TestEmptyStringAsNone = serde_json::from_str(json).unwrap();
1049        assert_eq!(result.value, expected);
1050    }
1051
1052    #[derive(Deserialize)]
1053    struct TestEmptyUstrAsNone {
1054        #[serde(deserialize_with = "deserialize_empty_ustr_as_none")]
1055        value: Option<Ustr>,
1056    }
1057
1058    #[rstest]
1059    #[case(r#"{"value":"hello"}"#, Some(Ustr::from("hello")))]
1060    #[case(r#"{"value":""}"#, None)]
1061    #[case(r#"{"value":null}"#, None)]
1062    fn test_deserialize_empty_ustr_as_none(#[case] json: &str, #[case] expected: Option<Ustr>) {
1063        let result: TestEmptyUstrAsNone = serde_json::from_str(json).unwrap();
1064        assert_eq!(result.value, expected);
1065    }
1066
1067    #[derive(Serialize, Deserialize, PartialEq, Debug)]
1068    struct TestVecDecimal {
1069        #[serde(
1070            serialize_with = "serialize_vec_decimal_as_str",
1071            deserialize_with = "deserialize_vec_decimal_from_str"
1072        )]
1073        values: Vec<Decimal>,
1074    }
1075
1076    #[rstest]
1077    fn test_vec_decimal_roundtrip() {
1078        let original = TestVecDecimal {
1079            values: vec![dec!(1.5), dec!(2.25), dec!(100.001)],
1080        };
1081
1082        let json = serde_json::to_string(&original).unwrap();
1083        assert!(json.contains("[\"1.5\",\"2.25\",\"100.001\"]"));
1084
1085        let parsed: TestVecDecimal = serde_json::from_str(&json).unwrap();
1086        assert_eq!(original.values, parsed.values);
1087    }
1088
1089    #[rstest]
1090    fn test_vec_decimal_empty() {
1091        let original = TestVecDecimal { values: vec![] };
1092
1093        let json = serde_json::to_string(&original).unwrap();
1094        let parsed: TestVecDecimal = serde_json::from_str(&json).unwrap();
1095        assert_eq!(original.values, parsed.values);
1096    }
1097
1098    #[derive(Deserialize)]
1099    struct TestStringToU8 {
1100        #[serde(deserialize_with = "deserialize_string_to_u8")]
1101        value: u8,
1102    }
1103
1104    #[rstest]
1105    #[case(r#"{"value":"42"}"#, 42)]
1106    #[case(r#"{"value":"0"}"#, 0)]
1107    #[case(r#"{"value":"255"}"#, 255)]
1108    #[case(r#"{"value":""}"#, 0)]
1109    fn test_deserialize_string_to_u8(#[case] json: &str, #[case] expected: u8) {
1110        let result: TestStringToU8 = serde_json::from_str(json).unwrap();
1111        assert_eq!(result.value, expected);
1112    }
1113
1114    #[rstest]
1115    #[case(r#"{"value":"256"}"#)]
1116    #[case(r#"{"value":"999"}"#)]
1117    #[case(r#"{"value":"abc"}"#)]
1118    fn test_deserialize_string_to_u8_invalid(#[case] json: &str) {
1119        let result: Result<TestStringToU8, _> = serde_json::from_str(json);
1120        assert!(result.is_err());
1121    }
1122
1123    #[derive(Deserialize)]
1124    struct TestStringToU64 {
1125        #[serde(deserialize_with = "deserialize_string_to_u64")]
1126        value: u64,
1127    }
1128
1129    #[rstest]
1130    #[case(r#"{"value":"12345678901234"}"#, 12_345_678_901_234)]
1131    #[case(r#"{"value":"0"}"#, 0)]
1132    #[case(r#"{"value":"18446744073709551615"}"#, u64::MAX)]
1133    #[case(r#"{"value":""}"#, 0)]
1134    fn test_deserialize_string_to_u64(#[case] json: &str, #[case] expected: u64) {
1135        let result: TestStringToU64 = serde_json::from_str(json).unwrap();
1136        assert_eq!(result.value, expected);
1137    }
1138
1139    #[rstest]
1140    #[case(r#"{"value":"18446744073709551616"}"#)]
1141    #[case(r#"{"value":"abc"}"#)]
1142    #[case(r#"{"value":"-1"}"#)]
1143    fn test_deserialize_string_to_u64_invalid(#[case] json: &str) {
1144        let result: Result<TestStringToU64, _> = serde_json::from_str(json);
1145        assert!(result.is_err());
1146    }
1147
1148    #[derive(Deserialize)]
1149    struct TestOptionalStringToU64 {
1150        #[serde(deserialize_with = "deserialize_optional_string_to_u64")]
1151        value: Option<u64>,
1152    }
1153
1154    #[rstest]
1155    #[case(r#"{"value":"12345678901234"}"#, Some(12_345_678_901_234))]
1156    #[case(r#"{"value":"0"}"#, Some(0))]
1157    #[case(r#"{"value":""}"#, None)]
1158    #[case(r#"{"value":null}"#, None)]
1159    fn test_deserialize_optional_string_to_u64(#[case] json: &str, #[case] expected: Option<u64>) {
1160        let result: TestOptionalStringToU64 = serde_json::from_str(json).unwrap();
1161        assert_eq!(result.value, expected);
1162    }
1163
1164    #[rstest]
1165    #[case("123.45", dec!(123.45))]
1166    #[case("0", Decimal::ZERO)]
1167    #[case("0.0", Decimal::ZERO)]
1168    fn test_parse_decimal(#[case] input: &str, #[case] expected: Decimal) {
1169        let result = parse_decimal(input).unwrap();
1170        assert_eq!(result, expected);
1171    }
1172
1173    #[rstest]
1174    fn test_parse_decimal_invalid() {
1175        assert!(parse_decimal("invalid").is_err());
1176        assert!(parse_decimal("").is_err());
1177    }
1178
1179    #[rstest]
1180    #[case(&Some("123.45".to_string()), Some(dec!(123.45)))]
1181    #[case(&Some("0".to_string()), Some(Decimal::ZERO))]
1182    #[case(&Some(String::new()), None)]
1183    #[case(&None, None)]
1184    fn test_parse_optional_decimal(
1185        #[case] input: &Option<String>,
1186        #[case] expected: Option<Decimal>,
1187    ) {
1188        let result = parse_optional_decimal(input).unwrap();
1189        assert_eq!(result, expected);
1190    }
1191
1192    // Tests for flexible decimal deserializers (handles both string and number JSON values)
1193
1194    #[derive(Debug, Serialize, Deserialize, PartialEq)]
1195    struct TestFlexibleDecimal {
1196        #[serde(
1197            serialize_with = "serialize_decimal",
1198            deserialize_with = "deserialize_decimal"
1199        )]
1200        value: Decimal,
1201        #[serde(
1202            serialize_with = "serialize_optional_decimal",
1203            deserialize_with = "deserialize_optional_decimal"
1204        )]
1205        optional_value: Option<Decimal>,
1206    }
1207
1208    #[rstest]
1209    #[case(r#"{"value": 123.456, "optional_value": 789.012}"#, dec!(123.456), Some(dec!(789.012)))]
1210    #[case(r#"{"value": "123.456", "optional_value": "789.012"}"#, dec!(123.456), Some(dec!(789.012)))]
1211    #[case(r#"{"value": 100, "optional_value": null}"#, dec!(100), None)]
1212    #[case(r#"{"value": null, "optional_value": null}"#, Decimal::ZERO, None)]
1213    fn test_deserialize_flexible_decimal(
1214        #[case] json: &str,
1215        #[case] expected_value: Decimal,
1216        #[case] expected_optional: Option<Decimal>,
1217    ) {
1218        let result: TestFlexibleDecimal = serde_json::from_str(json).unwrap();
1219        assert_eq!(result.value, expected_value);
1220        assert_eq!(result.optional_value, expected_optional);
1221    }
1222
1223    #[rstest]
1224    fn test_flexible_decimal_roundtrip() {
1225        let original = TestFlexibleDecimal {
1226            value: dec!(123.456),
1227            optional_value: Some(dec!(789.012)),
1228        };
1229
1230        let json = serde_json::to_string(&original).unwrap();
1231        let deserialized: TestFlexibleDecimal = serde_json::from_str(&json).unwrap();
1232
1233        assert_eq!(original.value, deserialized.value);
1234        assert_eq!(original.optional_value, deserialized.optional_value);
1235    }
1236
1237    #[rstest]
1238    fn test_flexible_decimal_scientific_notation() {
1239        // Test that scientific notation from serde_json is handled correctly.
1240        // serde_json outputs very small numbers like 0.00000001 as "1e-8".
1241        // Note: JSON numbers are parsed as f64, so values are limited to ~15 significant digits.
1242        let json = r#"{"value": 0.00000001, "optional_value": 12345678.12345}"#;
1243        let parsed: TestFlexibleDecimal = serde_json::from_str(json).unwrap();
1244        assert_eq!(parsed.value, dec!(0.00000001));
1245        assert_eq!(parsed.optional_value, Some(dec!(12345678.12345)));
1246    }
1247
1248    #[rstest]
1249    fn test_flexible_decimal_empty_string_optional() {
1250        let json = r#"{"value": 100, "optional_value": ""}"#;
1251        let parsed: TestFlexibleDecimal = serde_json::from_str(json).unwrap();
1252        assert_eq!(parsed.value, dec!(100));
1253        assert_eq!(parsed.optional_value, None);
1254    }
1255
1256    // Additional tests for DecimalVisitor edge cases
1257
1258    #[derive(Debug, Deserialize)]
1259    struct TestDecimalOnly {
1260        #[serde(deserialize_with = "deserialize_decimal")]
1261        value: Decimal,
1262    }
1263
1264    #[rstest]
1265    #[case(r#"{"value": "1.5e-8"}"#, dec!(0.000000015))]
1266    #[case(r#"{"value": "1E10"}"#, dec!(10000000000))]
1267    #[case(r#"{"value": "-1.23e5"}"#, dec!(-123000))]
1268    fn test_deserialize_decimal_scientific_string(#[case] json: &str, #[case] expected: Decimal) {
1269        let result: TestDecimalOnly = serde_json::from_str(json).unwrap();
1270        assert_eq!(result.value, expected);
1271    }
1272
1273    #[rstest]
1274    #[case(r#"{"value": 9223372036854775807}"#, dec!(9223372036854775807))] // i64::MAX
1275    #[case(r#"{"value": -9223372036854775808}"#, dec!(-9223372036854775808))] // i64::MIN
1276    #[case(r#"{"value": 0}"#, Decimal::ZERO)]
1277    fn test_deserialize_decimal_large_integers(#[case] json: &str, #[case] expected: Decimal) {
1278        let result: TestDecimalOnly = serde_json::from_str(json).unwrap();
1279        assert_eq!(result.value, expected);
1280    }
1281
1282    #[rstest]
1283    #[case(r#"{"value": "-123.456789"}"#, dec!(-123.456789))]
1284    #[case(r#"{"value": -999.99}"#, dec!(-999.99))]
1285    fn test_deserialize_decimal_negative(#[case] json: &str, #[case] expected: Decimal) {
1286        let result: TestDecimalOnly = serde_json::from_str(json).unwrap();
1287        assert_eq!(result.value, expected);
1288    }
1289
1290    #[rstest]
1291    #[case(r#"{"value": "123456789.123456789012345678"}"#)] // High precision string
1292    fn test_deserialize_decimal_high_precision(#[case] json: &str) {
1293        let result: TestDecimalOnly = serde_json::from_str(json).unwrap();
1294        assert_eq!(result.value, dec!(123456789.123456789012345678));
1295    }
1296
1297    #[rstest]
1298    #[case(
1299        r#"{"value": "1.234567890123456789012345678912345e-1"}"#,
1300        "0.1234567890123456789012345679"
1301    )]
1302    #[case(
1303        r#"{"value": "0.1234567890123456789012345678912345"}"#,
1304        "0.1234567890123456789012345679"
1305    )]
1306    #[case(
1307        r#"{"value": "999999999999999999999999999995e-29"}"#,
1308        "10.000000000000000000000000000"
1309    )]
1310    #[case(r#"{"value": "0.5e29"}"#, "50000000000000000000000000000")]
1311    #[case(r#"{"value": "-4e-29"}"#, "0.0000000000000000000000000000")]
1312    #[case(r#"{"value": "1.5e-999999"}"#, "0.0000000000000000000000000000")]
1313    #[case(r#"{"value": "9e-999999"}"#, "0.0000000000000000000000000000")]
1314    #[case(r#"{"value": "0e2000000000"}"#, "0")]
1315    fn test_deserialize_decimal_rounds_high_scale_values(
1316        #[case] json: &str,
1317        #[case] expected: &str,
1318    ) {
1319        // Carries propagate and a rounded-away negative loses its sign.
1320        let result: TestDecimalOnly = serde_json::from_str(json).unwrap();
1321        assert_eq!(result.value.to_string(), expected);
1322    }
1323
1324    #[rstest]
1325    #[rstest]
1326    fn test_deserialize_decimal_negative_rounding_to_zero_loses_sign() {
1327        let json = r#"{"value": "-1.5e-999999"}"#;
1328        let result: TestDecimalOnly = serde_json::from_str(json).unwrap();
1329        assert_eq!(result.value.to_string(), "0.0000000000000000000000000000");
1330    }
1331
1332    #[rstest]
1333    #[case(r#"{"value": "899999999999999999999999999995e-29"}"#)]
1334    #[case(r#"{"value": "+899999999999999999999999999995e-29"}"#)]
1335    fn test_deserialize_decimal_rounding_carries_into_integer(#[case] json: &str) {
1336        // Thirty-digit mantissas fail scientific parsing and round up into a non-nine digit
1337        let result: TestDecimalOnly = serde_json::from_str(json).unwrap();
1338        assert_eq!(result.value.to_string(), "9.000000000000000000000000000");
1339    }
1340
1341    #[rstest]
1342    #[case(r#"{"value": "8e28"}"#)] // above Decimal::MAX
1343    #[case(r#"{"value": "1e1000000000"}"#)] // absurd exponent must fail without expansion
1344    #[case(r#"{"value": "not-a-number"}"#)]
1345    #[case(r#"{"value": "1.2.3e1"}"#)] // multiple decimal points
1346    #[case(r#"{"value": ".e1"}"#)] // empty coefficient
1347    fn test_deserialize_decimal_rejects_unrepresentable_values(#[case] json: &str) {
1348        // The fallback rounds fractional digits only; oversized magnitudes
1349        // keep their original parse error.
1350        let result: Result<TestDecimalOnly, _> = serde_json::from_str(json);
1351        assert!(result.is_err());
1352    }
1353
1354    #[rstest]
1355    fn test_deserialize_optional_decimal_rounds_high_scale_values() {
1356        let json = r#"{"value": "51.234567890123456789012345678912345e-1"}"#;
1357        let result: TestOptionalDecimalOnly = serde_json::from_str(json).unwrap();
1358        assert_eq!(
1359            result.value.map(|v| v.to_string()),
1360            Some("5.1234567890123456789012345679".into()),
1361        );
1362    }
1363
1364    #[derive(Debug, Deserialize)]
1365    struct TestOptionalDecimalOnly {
1366        #[serde(deserialize_with = "deserialize_optional_decimal")]
1367        value: Option<Decimal>,
1368    }
1369
1370    #[rstest]
1371    #[case(r#"{"value": "1.5e-8"}"#, Some(dec!(0.000000015)))]
1372    #[case(r#"{"value": null}"#, None)]
1373    #[case(r#"{"value": ""}"#, None)]
1374    #[case(r#"{"value": 42}"#, Some(dec!(42)))]
1375    #[case(r#"{"value": -100.5}"#, Some(dec!(-100.5)))]
1376    fn test_deserialize_optional_decimal_various(
1377        #[case] json: &str,
1378        #[case] expected: Option<Decimal>,
1379    ) {
1380        let result: TestOptionalDecimalOnly = serde_json::from_str(json).unwrap();
1381        assert_eq!(result.value, expected);
1382    }
1383
1384    #[rstest]
1385    fn test_decimal_visitor_direct_owned_and_wide_integer_paths() {
1386        assert_eq!(
1387            DecimalVisitor
1388                .visit_string::<ValueError>("1.5".to_string())
1389                .unwrap(),
1390            dec!(1.5)
1391        );
1392        assert_eq!(
1393            DecimalVisitor.visit_i128::<ValueError>(5).unwrap(),
1394            Decimal::from(5i128)
1395        );
1396        assert_eq!(
1397            DecimalVisitor.visit_u128::<ValueError>(5).unwrap(),
1398            Decimal::from(5u128)
1399        );
1400        assert_eq!(
1401            DecimalVisitor.visit_none::<ValueError>().unwrap(),
1402            Decimal::ZERO
1403        );
1404    }
1405
1406    #[rstest]
1407    fn test_decimal_visitor_rejects_non_finite_float() {
1408        assert!(
1409            DecimalVisitor
1410                .visit_f64::<ValueError>(f64::INFINITY)
1411                .is_err()
1412        );
1413        assert!(
1414            DecimalVisitor
1415                .visit_f64::<ValueError>(f64::NEG_INFINITY)
1416                .is_err()
1417        );
1418        assert!(DecimalVisitor.visit_f64::<ValueError>(f64::NAN).is_err());
1419    }
1420
1421    #[rstest]
1422    fn test_optional_decimal_visitor_direct_paths() {
1423        assert_eq!(
1424            OptionalDecimalVisitor
1425                .visit_string::<ValueError>("1.5".to_string())
1426                .unwrap(),
1427            Some(dec!(1.5))
1428        );
1429        assert_eq!(
1430            OptionalDecimalVisitor.visit_i64::<ValueError>(42).unwrap(),
1431            Some(dec!(42))
1432        );
1433        assert_eq!(
1434            OptionalDecimalVisitor.visit_i128::<ValueError>(5).unwrap(),
1435            Some(Decimal::from(5i128))
1436        );
1437        assert_eq!(
1438            OptionalDecimalVisitor.visit_u128::<ValueError>(5).unwrap(),
1439            Some(Decimal::from(5u128))
1440        );
1441        assert_eq!(
1442            OptionalDecimalVisitor.visit_none::<ValueError>().unwrap(),
1443            None
1444        );
1445    }
1446
1447    #[rstest]
1448    fn test_serialize_optional_decimal_none_serializes_null() {
1449        let original = TestFlexibleDecimal {
1450            value: dec!(1),
1451            optional_value: None,
1452        };
1453
1454        let json = serde_json::to_string(&original).unwrap();
1455        let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1456        assert_eq!(parsed["optional_value"], json!(null));
1457    }
1458
1459    #[rstest]
1460    fn test_serde_bool_defaults() {
1461        assert!(default_true());
1462        assert!(!default_false());
1463    }
1464
1465    use proptest::prelude::*;
1466    use rust_decimal::prelude::ToPrimitive;
1467
1468    fn representable_decimal_strategy() -> impl Strategy<Value = Decimal> {
1469        // Mantissa spans Decimal's full 96-bit range; every generated value is
1470        // exactly representable.
1471        (
1472            -79_228_162_514_264_337_593_543_950_335i128
1473                ..=79_228_162_514_264_337_593_543_950_335i128,
1474            0u32..=28u32,
1475        )
1476            .prop_map(|(mantissa, scale)| Decimal::from_i128_with_scale(mantissa, scale))
1477    }
1478
1479    fn numeric_string_strategy() -> impl Strategy<Value = String> {
1480        // Signed digit strings with long fractions and exponents, covering
1481        // both natively-parsed shapes and the high-scale clamp fallback.
1482        (
1483            proptest::bool::ANY,
1484            "[0-9]{1,30}",
1485            proptest::option::of("[0-9]{1,40}"),
1486            proptest::option::of(-40i32..=40),
1487        )
1488            .prop_map(|(negative, integer, fraction, exponent)| {
1489                let mut value = String::new();
1490                if negative {
1491                    value.push('-');
1492                }
1493                value.push_str(&integer);
1494                if let Some(fraction) = fraction {
1495                    value.push('.');
1496                    value.push_str(&fraction);
1497                }
1498
1499                if let Some(exponent) = exponent {
1500                    value.push('e');
1501                    value.push_str(&exponent.to_string());
1502                }
1503                value
1504            })
1505    }
1506
1507    proptest! {
1508        #[rstest]
1509        fn prop_deserialize_decimal_roundtrips_representable_values(
1510            expected in representable_decimal_strategy()
1511        ) {
1512            // The clamp fallback must never distort a value Decimal can hold
1513            // exactly.
1514            let json = format!(r#"{{"value": "{expected}"}}"#);
1515            let parsed: TestDecimalOnly = serde_json::from_str(&json).unwrap();
1516            prop_assert_eq!(parsed.value, expected);
1517        }
1518
1519        #[rstest]
1520        fn prop_deserialize_decimal_total_on_arbitrary_strings(value in "\\PC{0,64}") {
1521            // Any string input must decode or error without panicking.
1522            let json = serde_json::to_string(&serde_json::json!({"value": value})).unwrap();
1523            let _ = serde_json::from_str::<TestDecimalOnly>(&json);
1524        }
1525
1526        #[rstest]
1527        fn prop_deserialize_decimal_tracks_f64_reference(value in numeric_string_strategy()) {
1528            // Accepted values (rounded or not) must agree with an independent
1529            // f64 parse of the same string within f64 precision.
1530            let json = format!(r#"{{"value": "{value}"}}"#);
1531            if let Ok(parsed) = serde_json::from_str::<TestDecimalOnly>(&json) {
1532                let reference: f64 = value.parse().unwrap();
1533                let decoded = parsed.value.to_f64().unwrap();
1534                prop_assert!(
1535                    (decoded - reference).abs() <= reference.abs() * 1e-9 + 1e-27,
1536                    "decoded {decoded} diverges from reference {reference} for input {value}",
1537                );
1538            }
1539        }
1540
1541        #[rstest]
1542        fn prop_deserialize_optional_decimal_matches_required(
1543            value in numeric_string_strategy()
1544        ) {
1545            let json = format!(r#"{{"value": "{value}"}}"#);
1546            let required = serde_json::from_str::<TestDecimalOnly>(&json);
1547            let optional = serde_json::from_str::<TestOptionalDecimalOnly>(&json);
1548            match (required, optional) {
1549                (Ok(required), Ok(optional)) => {
1550                    prop_assert_eq!(optional.value, Some(required.value));
1551                }
1552                (Err(_), Err(_)) => {}
1553                (required, optional) => prop_assert!(
1554                    false,
1555                    "required and optional decoding disagree for input {}: {:?} vs {:?}",
1556                    value,
1557                    required.map(|r| r.value),
1558                    optional.map(|o| o.value),
1559                ),
1560            }
1561        }
1562    }
1563}