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