Skip to main content

nautilus_core/serialization/
decimal.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//! Exact decimal parsing and JSON serialization contracts.
17//!
18//! Exact decimal ingestion, separate from instrument and currency rounding.
19//!
20//! [`deserialize_json`] preserves quoted and unquoted JSON tokens with the default features.
21//! It uses `RawValue`, which Serde's internally tagged and untagged enum buffers do not support.
22//!
23//! Use [`deserialize`] for those buffers: strings and 64-bit integers work with all feature sets.
24//! Larger integers, fractional tokens, and scientific numeric tokens require
25//! `serde_json/arbitrary_precision` from the first JSON parse. Floats are rejected because their
26//! original decimal digits are unavailable.
27//! Even with arbitrary precision, `Number`'s Serde deserializer delivers some decimals as floats.
28//! For buffered `Value` use [`deserialize_json`]; for `Number` use `parse(&number.to_string())`.
29//! These routes preserve the buffered decimal text when arbitrary precision was enabled at ingest.
30//! Serde enum buffering of a `Value` may still deliver floats, which are rejected.
31//!
32//! The visitor also accepts `serde_json`'s private number-map encoding with arbitrary precision enabled.
33//! A literal JSON object using that reserved key is indistinguishable from the numeric encoding.
34//! Use the raw-token route before buffering when every JSON object must be rejected.
35//! With arbitrary precision, `Value` parsing itself interprets the reserved object as a number.
36//!
37//! A `Value` or `Number` created through floating-point conversion has already lost its source
38//! spelling. No API here recovers those digits. In particular, passing a default-feature `Value`
39//! to [`deserialize_json`] reparses its rendered value, not the original JSON token.
40//!
41//! Required fields reject missing, null, empty, invalid, overflowing, and underflowing values.
42//! Optional fields accept null as `None`; add `#[serde(default)]` to accept missing fields.
43//! Empty strings remain errors. Representability concerns numeric value, so redundant trailing
44//! zeros may be removed. Instrument precision and currency rounding belong to model constructors.
45//!
46//! [`serialize`] emits exact JSON numbers, including through `serde_json::to_value`. Enable
47//! `serde_json/arbitrary_precision` for the full Decimal range. Without it, values whose numeric
48//! value would change are errors. String output uses
49//! [`serialize_decimal_as_str`](crate::serialization::serialize_decimal_as_str) and
50//! [`serialize_optional_decimal_as_str`](crate::serialization::serialize_optional_decimal_as_str).
51//! These JSON number contracts do not extend to MessagePack or other Serde formats.
52//! The parent module's permissive deserializers accept null and empty strings, round excess
53//! fractional digits, and preserve exactly representable scientific strings.
54//! Oversized integer inputs return errors.
55
56use std::str::FromStr;
57
58use rust_decimal::Decimal;
59use serde::{
60    Deserialize, Deserializer, Serialize, Serializer,
61    de::{Error, MapAccess, Visitor, value::MapAccessDeserializer},
62};
63use serde_json::{Number, value::RawValue};
64
65use super::{DecimalVisitor, decimal_components, json_token_text};
66
67/// Deserializes an exact decimal from strings, integers, or arbitrary-precision number maps.
68///
69/// Supports direct JSON and Serde enum buffers. Integers outside the 64-bit range, fractional
70/// tokens, and scientific numeric tokens require `serde_json/arbitrary_precision`.
71/// Use [`deserialize_json`] for default-feature JSON or buffered
72/// `Value`. Some buffered `Number` values use Serde's float route even with arbitrary precision;
73/// parse their text with [`parse`] instead.
74/// With arbitrary precision, accepts `serde_json`'s private number maps, including literal objects
75/// with the same reserved key.
76/// Use [`deserialize_json`] before buffering when the original JSON shape must be checked.
77///
78/// # Errors
79///
80/// Returns an error if:
81/// - The input is a float, null, an empty string, or an invalid decimal.
82/// - Representing the value requires rounding.
83pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Decimal, D::Error> {
84    deserializer.deserialize_any(ExactDecimalVisitor)
85}
86
87/// Deserializes an optional exact decimal, accepting null as `None`.
88///
89/// Add `#[serde(default)]` to accept a missing field. Other values follow [`deserialize`].
90///
91/// # Errors
92///
93/// Returns an error if [`deserialize`] rejects a non-null value.
94pub fn deserialize_optional<'de, D: Deserializer<'de>>(
95    deserializer: D,
96) -> Result<Option<Decimal>, D::Error> {
97    deserializer.deserialize_option(OptionalExactDecimalVisitor)
98}
99
100/// Deserializes an exact decimal directly from a quoted or unquoted JSON token.
101///
102/// Works without arbitrary precision. Use before `Value` buffering to preserve source digits.
103/// Serde internally tagged and untagged enum buffers do not support this raw-token route.
104///
105/// # Errors
106///
107/// Returns an error if:
108/// - The input is null, an empty string, a non-decimal shape, or an invalid decimal.
109/// - Representing the value requires rounding.
110pub fn deserialize_json<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Decimal, D::Error> {
111    let raw = Box::<RawValue>::deserialize(deserializer)?;
112    parse_json(&raw).map_err(D::Error::custom)
113}
114
115/// Deserializes an optional exact JSON decimal, accepting null as `None`.
116///
117/// Add `#[serde(default)]` to accept a missing field. Other values follow [`deserialize_json`].
118///
119/// # Errors
120///
121/// Returns an error if [`deserialize_json`] rejects a non-null value.
122pub fn deserialize_optional_json<'de, D: Deserializer<'de>>(
123    deserializer: D,
124) -> Result<Option<Decimal>, D::Error> {
125    Option::<Box<RawValue>>::deserialize(deserializer)?
126        .map(|raw| parse_json(&raw).map_err(D::Error::custom))
127        .transpose()
128}
129
130/// Serializes a decimal as a JSON number without changing its numeric value.
131///
132/// Checks the `Number` representation before serialization so `to_value` is also lossless.
133/// The full Decimal range requires `serde_json/arbitrary_precision`. Without that feature,
134/// scale is not preserved and output may use exponent notation. Check the receiving API's
135/// numeric syntax before using this serializer for wire payloads.
136///
137/// Raw-token serializers can emit the full Decimal range directly to JSON text without arbitrary
138/// precision. Replacing them with this serializer can reject previously supported values unless
139/// `serde_json/arbitrary_precision` is enabled. Use [`super::serialize_decimal_as_str`] for
140/// portable string output.
141///
142/// # Errors
143///
144/// Returns an error if:
145/// - The enabled JSON representation would round the value.
146/// - Serialization fails.
147pub fn serialize<S: Serializer>(value: &Decimal, serializer: S) -> Result<S::Ok, S::Error> {
148    let number = Number::from_str(&value.to_string()).map_err(serde::ser::Error::custom)?;
149    let represented = parse(&number.to_string()).map_err(serde::ser::Error::custom)?;
150    if represented != *value {
151        return Err(serde::ser::Error::custom(
152            "exact decimal number serialization requires serde_json/arbitrary_precision",
153        ));
154    }
155
156    number.serialize(serializer)
157}
158
159/// Serializes an optional decimal as an exact JSON number or null.
160///
161/// # Errors
162///
163/// Returns an error if [`serialize`] fails, or if serializing null fails.
164pub fn serialize_optional<S: Serializer>(
165    value: &Option<Decimal>,
166    serializer: S,
167) -> Result<S::Ok, S::Error> {
168    match value {
169        Some(value) => serialize(value, serializer),
170        None => serializer.serialize_none(),
171    }
172}
173
174/// Parses a decimal or scientific string without rounding its numeric value.
175///
176/// Accepts signs, decimal points, and digit separators supported by `rust_decimal`.
177/// Scientific input is normalized. Plain input drops redundant trailing zeros only when needed
178/// to fit Decimal's mantissa or scale.
179/// Scientific exponents must fit `i64`.
180///
181/// # Errors
182///
183/// Returns an error if:
184/// - The input is not a valid decimal or scientific string.
185/// - The value exceeds Decimal's exact mantissa or scale bounds.
186pub fn parse(value: &str) -> Result<Decimal, rust_decimal::Error> {
187    // `from_str_exact` rejects exponent markers, so plain input needs no scan for them first
188    if let Ok(value) = Decimal::from_str_exact(value) {
189        return Ok(value);
190    }
191
192    // Normalize the significand before construction: from_scientific parses its base with
193    // the rounding FromStr implementation, even when the final value is representable.
194    let (base, exponent) = match value.split_once(['e', 'E']) {
195        Some((base, exponent)) => (
196            base,
197            exponent
198                .parse::<i64>()
199                .map_err(|_| rust_decimal::Error::from("invalid decimal exponent"))?,
200        ),
201        None => (value, 0),
202    };
203
204    let negative = base.starts_with('-');
205    let base = base.strip_prefix(['-', '+']).unwrap_or(base);
206    if base.starts_with('_') || base.starts_with("._") {
207        return Err(rust_decimal::Error::from("invalid decimal significand"));
208    }
209
210    let base = base.replace('_', "");
211    let (whole, fraction) = decimal_components(&base)
212        .ok_or_else(|| rust_decimal::Error::from("invalid decimal significand"))?;
213    let mut digits = format!("{whole}{fraction}");
214    let mut scale = i64::try_from(fraction.len())
215        .ok()
216        .and_then(|scale| scale.checked_sub(exponent))
217        .ok_or_else(|| rust_decimal::Error::from("decimal scale out of range"))?;
218    digits = digits.trim_start_matches('0').to_string();
219    if digits.is_empty() {
220        return Ok(Decimal::ZERO);
221    }
222
223    while scale > 0 && digits.ends_with('0') {
224        digits.pop();
225        scale -= 1;
226    }
227
228    if scale < 0 {
229        let zeros = scale
230            .checked_neg()
231            .and_then(|scale| usize::try_from(scale).ok())
232            .filter(|zeros| digits.len().saturating_add(*zeros) <= 29)
233            .ok_or(rust_decimal::Error::ExceedsMaximumPossibleValue)?;
234        digits.extend(std::iter::repeat_n('0', zeros));
235        scale = 0;
236    }
237
238    let scale = u32::try_from(scale).map_err(|_| rust_decimal::Error::Underflow)?;
239    if scale > Decimal::MAX_SCALE {
240        return Err(rust_decimal::Error::Underflow);
241    }
242
243    let mantissa = digits
244        .parse::<i128>()
245        .map_err(|_| rust_decimal::Error::ExceedsMaximumPossibleValue)?;
246    Decimal::try_from_i128_with_scale(if negative { -mantissa } else { mantissa }, scale)
247}
248
249fn parse_json(raw: &RawValue) -> Result<Decimal, String> {
250    let text = json_token_text(raw).map_err(|e| e.to_string())?;
251    parse(&text).map_err(|e| e.to_string())
252}
253
254struct ExactDecimalVisitor;
255
256impl<'de> Visitor<'de> for ExactDecimalVisitor {
257    type Value = Decimal;
258
259    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
260        formatter
261            .write_str("an exact decimal string or integer, or an arbitrary-precision JSON number")
262    }
263
264    fn visit_str<E: Error>(self, value: &str) -> Result<Self::Value, E> {
265        parse(value).map_err(E::custom)
266    }
267
268    fn visit_i64<E: Error>(self, value: i64) -> Result<Self::Value, E> {
269        DecimalVisitor.visit_i64(value)
270    }
271
272    fn visit_u64<E: Error>(self, value: u64) -> Result<Self::Value, E> {
273        DecimalVisitor.visit_u64(value)
274    }
275
276    fn visit_i128<E: Error>(self, value: i128) -> Result<Self::Value, E> {
277        DecimalVisitor.visit_i128(value)
278    }
279
280    fn visit_u128<E: Error>(self, value: u128) -> Result<Self::Value, E> {
281        DecimalVisitor.visit_u128(value)
282    }
283
284    fn visit_map<A: MapAccess<'de>>(self, map: A) -> Result<Self::Value, A::Error> {
285        let number = Number::deserialize(MapAccessDeserializer::new(map))?;
286        parse(&number.to_string()).map_err(A::Error::custom)
287    }
288}
289
290struct OptionalExactDecimalVisitor;
291
292impl<'de> Visitor<'de> for OptionalExactDecimalVisitor {
293    type Value = Option<Decimal>;
294
295    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
296        formatter.write_str("null or an exact decimal")
297    }
298
299    fn visit_none<E: Error>(self) -> Result<Self::Value, E> {
300        Ok(None)
301    }
302
303    fn visit_unit<E: Error>(self) -> Result<Self::Value, E> {
304        Ok(None)
305    }
306
307    fn visit_some<D: Deserializer<'de>>(self, deserializer: D) -> Result<Self::Value, D::Error> {
308        deserialize(deserializer).map(Some)
309    }
310}
311
312#[cfg(test)]
313mod tests {
314    use std::{collections::BTreeMap, str::FromStr};
315
316    use proptest::prelude::*;
317    use rstest::rstest;
318    use rust_decimal::Decimal;
319    use rust_decimal_macros::dec;
320    use serde::{
321        Deserialize, Serialize,
322        de::value::{Error, F64Deserializer, I128Deserializer, U128Deserializer},
323    };
324    use serde_json::{Number, Value, value::RawValue};
325
326    use crate::serialization::{self, decimal};
327
328    #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
329    struct Exact(#[serde(with = "decimal")] Decimal);
330
331    #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
332    struct Raw(
333        #[serde(
334            deserialize_with = "decimal::deserialize_json",
335            serialize_with = "decimal::serialize"
336        )]
337        Decimal,
338    );
339
340    #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
341    struct Optional {
342        #[serde(
343            default,
344            deserialize_with = "decimal::deserialize_optional",
345            serialize_with = "decimal::serialize_optional"
346        )]
347        value: Option<Decimal>,
348    }
349
350    #[derive(Debug, PartialEq, Eq, Deserialize)]
351    struct OptionalRaw {
352        #[serde(default, deserialize_with = "decimal::deserialize_optional_json")]
353        value: Option<Decimal>,
354    }
355
356    #[derive(Debug, Deserialize)]
357    struct Required {
358        #[serde(deserialize_with = "decimal::deserialize")]
359        value: Decimal,
360    }
361
362    #[derive(Debug, Deserialize)]
363    struct RequiredRaw {
364        #[serde(deserialize_with = "decimal::deserialize_json")]
365        value: Decimal,
366    }
367
368    #[derive(Debug, PartialEq, Eq, Deserialize)]
369    #[serde(tag = "kind")]
370    enum Tagged {
371        Amount {
372            #[serde(deserialize_with = "decimal::deserialize")]
373            value: Decimal,
374        },
375    }
376
377    #[derive(Debug, PartialEq, Eq, Deserialize)]
378    #[serde(untagged)]
379    enum Untagged {
380        Amount {
381            #[serde(deserialize_with = "decimal::deserialize")]
382            value: Decimal,
383        },
384    }
385
386    #[derive(Debug, Deserialize)]
387    #[serde(tag = "kind")]
388    enum TaggedRaw {
389        Amount {
390            #[serde(rename = "value", deserialize_with = "decimal::deserialize_json")]
391            _value: Decimal,
392        },
393    }
394
395    #[derive(Debug, Deserialize)]
396    #[serde(untagged)]
397    enum UntaggedRaw {
398        Amount {
399            #[serde(rename = "value", deserialize_with = "decimal::deserialize_json")]
400            _value: Decimal,
401        },
402    }
403
404    fn cases() -> [(&'static str, Decimal, bool); 22] {
405        [
406            ("123456789.123456789", dec!(123456789.123456789), false),
407            ("0.12345678901234568", dec!(0.12345678901234568), true),
408            ("9007199254740993", dec!(9007199254740993), true),
409            ("18446744073709551617", dec!(18446744073709551617), false),
410            (
411                "79228162514264337593543950335",
412                dec!(79228162514264337593543950335),
413                false,
414            ),
415            (
416                "-79228162514264337593543950335",
417                dec!(-79228162514264337593543950335),
418                false,
419            ),
420            ("1e-28", dec!(0.0000000000000000000000000001), false),
421            ("10e-29", dec!(0.0000000000000000000000000001), false),
422            ("1e28", dec!(10000000000000000000000000000), true),
423            ("0.1e29", dec!(10000000000000000000000000000), true),
424            (
425                "792281625142643375935439503350e-1",
426                dec!(79228162514264337593543950335),
427                false,
428            ),
429            (
430                "7.9228162514264337593543950335e28",
431                dec!(79228162514264337593543950335),
432                false,
433            ),
434            (
435                "0.00000000000000000000000000010",
436                dec!(0.0000000000000000000000000001),
437                false,
438            ),
439            (
440                "79228162514264337593543950335.0",
441                dec!(79228162514264337593543950335),
442                false,
443            ),
444            (
445                "1.234567890123456789012345678900E+1",
446                dec!(12.345678901234567890123456789),
447                false,
448            ),
449            ("-0.0", dec!(0), true),
450            ("0e1000000", dec!(0), true),
451            ("-3.75E+2", dec!(-375), true),
452            ("-375", dec!(-375), true),
453            (
454                "0.00000000000000000000000000001e1",
455                dec!(0.0000000000000000000000000001),
456                false,
457            ),
458            ("-12345e-4", dec!(-1.2345), true),
459            (
460                "1.234567890123456789012345678e-1",
461                dec!(0.1234567890123456789012345678),
462                false,
463            ),
464        ]
465    }
466
467    fn arbitrary_precision() -> bool {
468        Number::from_str(&Decimal::MAX.to_string())
469            .unwrap()
470            .to_string()
471            == Decimal::MAX.to_string()
472    }
473
474    fn object(token: &str, tagged: bool) -> String {
475        let mut fields = BTreeMap::new();
476        fields.insert("value", RawValue::from_string(token.to_string()).unwrap());
477
478        if tagged {
479            fields.insert(
480                "kind",
481                RawValue::from_string(serde_json::to_string("Amount").unwrap()).unwrap(),
482            );
483        }
484
485        serde_json::to_string(&fields).unwrap()
486    }
487
488    #[rstest]
489    fn exact_quoted_and_numeric_tokens() {
490        for (token, expected, _) in cases() {
491            let quoted = serde_json::to_string(&token).unwrap();
492            assert_eq!(decimal::parse(token).unwrap(), expected, "{token}");
493            assert_eq!(serde_json::from_str::<Raw>(token).unwrap(), Raw(expected));
494            assert_eq!(
495                serde_json::from_slice::<Raw>(token.as_bytes()).unwrap(),
496                Raw(expected)
497            );
498            assert_eq!(
499                serde_json::from_reader::<_, Raw>(token.as_bytes()).unwrap(),
500                Raw(expected)
501            );
502            assert_eq!(serde_json::from_str::<Raw>(&quoted).unwrap(), Raw(expected));
503            assert_eq!(
504                serde_json::from_str::<Exact>(&quoted).unwrap(),
505                Exact(expected)
506            );
507            let number: Number = serde_json::from_str(token).unwrap();
508            let direct = serde_json::from_str::<Exact>(token);
509            let buffered = serde_json::from_value::<Exact>(Value::Number(number.clone()));
510            let from_number = decimal::deserialize(number.clone());
511            if arbitrary_precision() || number.is_i64() || number.is_u64() {
512                assert_eq!(direct.unwrap(), Exact(expected));
513                assert_eq!(
514                    serde_json::from_value::<Raw>(Value::Number(number.clone())).unwrap(),
515                    Raw(expected)
516                );
517                assert_eq!(decimal::parse(&number.to_string()).unwrap(), expected);
518
519                match (buffered, from_number) {
520                    (Ok(buffered), Ok(from_number)) => {
521                        assert_eq!(buffered, Exact(expected));
522                        assert_eq!(from_number, expected);
523                    }
524                    (Err(buffered), Err(from_number)) => {
525                        assert!(buffered.to_string().contains("floating point"));
526                        assert!(from_number.to_string().contains("floating point"));
527                    }
528                    results => panic!("buffered routes disagree: {results:?}"),
529                }
530            } else {
531                assert!(direct.is_err(), "{token}");
532                assert!(buffered.is_err(), "{token}");
533                assert!(from_number.is_err(), "{token}");
534            }
535        }
536    }
537
538    #[rstest]
539    fn invalid_values_and_shapes() {
540        for token in [
541            "79228162514264337593543950336",
542            "-79228162514264337593543950336",
543            "1e29",
544            "1e-29",
545            "-1e-29",
546            "0.00000000000000000000000000001",
547            "1.00000000000000000000000000001",
548            "79228162514264337593543950335.1",
549            "1e9223372036854775807",
550            "1e-9223372036854775808",
551            "1e9223372036854775808",
552            "1.2.3e1",
553            "",
554            "NaN",
555            "Infinity",
556            "1e",
557            "1e1e1",
558            "--1",
559            "0.12345678901234567890123456789e0",
560            "0.12345678901234567890123456789",
561            "--1e0",
562            "_1",
563            "._1",
564            "_1e1",
565            "._1e1",
566        ] {
567            assert!(decimal::parse(token).is_err(), "{token}");
568            let quoted = serde_json::to_string(&token).unwrap();
569            assert!(serde_json::from_str::<Raw>(&quoted).is_err(), "{token}");
570            assert!(serde_json::from_str::<Exact>(&quoted).is_err(), "{token}");
571            assert!(serde_json::from_str::<Raw>(token).is_err(), "{token}");
572            assert!(serde_json::from_str::<Exact>(token).is_err(), "{token}");
573            let json = object(&quoted, false);
574            assert!(serde_json::from_str::<Optional>(&json).is_err(), "{token}");
575            assert!(
576                serde_json::from_str::<OptionalRaw>(&json).is_err(),
577                "{token}"
578            );
579
580            if let Ok(value) = serde_json::from_str::<Value>(token) {
581                assert!(
582                    serde_json::from_value::<Exact>(value.clone()).is_err(),
583                    "{token}"
584                );
585
586                if arbitrary_precision() {
587                    assert!(serde_json::from_value::<Raw>(value).is_err(), "{token}");
588                }
589            }
590        }
591
592        for token in ["null", "true", "false", "[]", "{}", "{\"amount\":1}"] {
593            assert!(serde_json::from_str::<Raw>(token).is_err(), "{token}");
594            assert!(serde_json::from_str::<Exact>(token).is_err(), "{token}");
595        }
596    }
597
598    #[rstest]
599    #[case("+1_234.50E-2", dec!(12.345))]
600    #[case("1.2_5", dec!(1.25))]
601    #[case(".5e1", dec!(5))]
602    fn quoted_decimal_syntax(#[case] token: &str, #[case] expected: Decimal) {
603        let json = serde_json::to_string(token).unwrap();
604        assert_eq!(decimal::parse(token).unwrap(), expected);
605        assert_eq!(serde_json::from_str::<Raw>(&json).unwrap(), Raw(expected));
606        assert_eq!(
607            serde_json::from_str::<Exact>(&json).unwrap(),
608            Exact(expected)
609        );
610    }
611
612    #[rstest]
613    fn quoted_json_escapes() {
614        assert_eq!(
615            serde_json::from_str::<Raw>(r#""\u0031.25""#).unwrap(),
616            Raw(dec!(1.25))
617        );
618        assert!(serde_json::from_str::<Raw>(r#""\uD800""#).is_err());
619    }
620
621    #[rstest]
622    fn private_number_map_requires_raw_shape_validation() {
623        let private_number = r#"{"$serde_json::private::Number":"123456789.123456789"}"#;
624        assert!(serde_json::from_str::<Raw>(private_number).is_err());
625        let direct = serde_json::from_str::<Exact>(private_number);
626        let value: Value = serde_json::from_str(private_number).unwrap();
627
628        if arbitrary_precision() {
629            assert_eq!(direct.unwrap(), Exact(dec!(123456789.123456789)));
630            assert!(value.is_number());
631            assert_eq!(
632                serde_json::from_value::<Raw>(value).unwrap(),
633                Raw(dec!(123456789.123456789))
634            );
635        } else {
636            assert!(direct.is_err());
637            assert!(value.is_object());
638            assert!(serde_json::from_value::<Raw>(value).is_err());
639        }
640
641        for token in [
642            "{\"$serde_json::private::Number\":\"not-a-number\"}",
643            "{\"$serde_json::private::Number\":\"1e-29\"}",
644            "{\"$serde_json::private::Number\":\"79228162514264337593543950336\"}",
645        ] {
646            assert!(serde_json::from_str::<Raw>(token).is_err());
647            assert!(serde_json::from_str::<Exact>(token).is_err());
648        }
649    }
650
651    #[rstest]
652    fn presence_semantics() {
653        for json in ["{}", r#"{"value":null}"#] {
654            assert!(serde_json::from_str::<Required>(json).is_err());
655            assert!(serde_json::from_str::<RequiredRaw>(json).is_err());
656            assert_eq!(
657                serde_json::from_str::<Optional>(json).unwrap(),
658                Optional { value: None }
659            );
660            assert_eq!(
661                serde_json::from_str::<OptionalRaw>(json).unwrap(),
662                OptionalRaw { value: None }
663            );
664        }
665
666        let empty = r#"{"value":""}"#;
667        assert!(serde_json::from_str::<Required>(empty).is_err());
668        assert!(serde_json::from_str::<RequiredRaw>(empty).is_err());
669        assert!(serde_json::from_str::<Optional>(empty).is_err());
670        assert!(serde_json::from_str::<OptionalRaw>(empty).is_err());
671
672        for (token, expected, _) in cases() {
673            let json = object(&serde_json::to_string(&token).unwrap(), false);
674            assert_eq!(
675                serde_json::from_str::<Required>(&json).unwrap().value,
676                expected
677            );
678            assert_eq!(
679                serde_json::from_str::<RequiredRaw>(&json).unwrap().value,
680                expected
681            );
682            assert_eq!(
683                serde_json::from_str::<Optional>(&json).unwrap().value,
684                Some(expected)
685            );
686            assert_eq!(
687                serde_json::from_str::<OptionalRaw>(&json).unwrap().value,
688                Some(expected)
689            );
690            let numeric = object(token, false);
691            assert_eq!(
692                serde_json::from_str::<OptionalRaw>(&numeric).unwrap().value,
693                Some(expected)
694            );
695            let optional = serde_json::from_str::<Optional>(&numeric);
696            let number = Number::from_str(token).unwrap();
697            if arbitrary_precision() || number.is_i64() || number.is_u64() {
698                assert_eq!(optional.unwrap().value, Some(expected));
699            } else {
700                assert!(optional.is_err());
701            }
702        }
703    }
704
705    #[rstest]
706    fn serde_enum_buffering() {
707        for (token, expected, _) in cases() {
708            let quoted = serde_json::to_string(&token).unwrap();
709            let tagged = object(&quoted, true);
710            let untagged = object(&quoted, false);
711            assert_eq!(
712                serde_json::from_str::<Tagged>(&tagged).unwrap(),
713                Tagged::Amount { value: expected }
714            );
715            assert_eq!(
716                serde_json::from_str::<Untagged>(&untagged).unwrap(),
717                Untagged::Amount { value: expected }
718            );
719            assert!(serde_json::from_str::<TaggedRaw>(&tagged).is_err());
720            assert!(serde_json::from_str::<UntaggedRaw>(&untagged).is_err());
721            let tagged = serde_json::from_str::<Tagged>(&object(token, true));
722            let untagged = serde_json::from_str::<Untagged>(&object(token, false));
723            let number = Number::from_str(token).unwrap();
724            if arbitrary_precision() || number.is_i64() || number.is_u64() {
725                assert_eq!(tagged.unwrap(), Tagged::Amount { value: expected });
726                assert_eq!(untagged.unwrap(), Untagged::Amount { value: expected });
727            } else {
728                assert!(tagged.is_err());
729                assert!(untagged.is_err());
730            }
731        }
732    }
733
734    #[rstest]
735    #[case(Decimal::MAX.mantissa(), Some(Decimal::MAX))]
736    #[case(Decimal::MIN.mantissa(), Some(Decimal::MIN))]
737    #[case(Decimal::MAX.mantissa() + 1, None)]
738    #[case(Decimal::MIN.mantissa() - 1, None)]
739    #[case(i128::MAX, None)]
740    #[case(i128::MIN, None)]
741    fn signed_integer_bounds(#[case] value: i128, #[case] expected: Option<Decimal>) {
742        assert_eq!(
743            decimal::deserialize(I128Deserializer::<Error>::new(value)).ok(),
744            expected
745        );
746        assert_eq!(
747            serialization::deserialize_decimal(I128Deserializer::<Error>::new(value)).ok(),
748            expected
749        );
750        assert_eq!(
751            serialization::deserialize_optional_decimal(I128Deserializer::<Error>::new(value)).ok(),
752            expected.map(Some)
753        );
754    }
755
756    #[rstest]
757    #[case(9_007_199_254_740_993, Some(dec!(9007199254740993)))]
758    #[case(18_446_744_073_709_551_617, Some(dec!(18446744073709551617)))]
759    #[case(Decimal::MAX.mantissa().unsigned_abs(), Some(Decimal::MAX))]
760    #[case(Decimal::MAX.mantissa().unsigned_abs() + 1, None)]
761    #[case(u128::MAX, None)]
762    fn unsigned_integer_bounds(#[case] value: u128, #[case] expected: Option<Decimal>) {
763        assert_eq!(
764            decimal::deserialize(U128Deserializer::<Error>::new(value)).ok(),
765            expected
766        );
767        assert_eq!(
768            serialization::deserialize_decimal(U128Deserializer::<Error>::new(value)).ok(),
769            expected
770        );
771        assert_eq!(
772            serialization::deserialize_optional_decimal(U128Deserializer::<Error>::new(value)).ok(),
773            expected.map(Some)
774        );
775    }
776
777    #[rstest]
778    #[case(0.5)]
779    #[case(f64::NAN)]
780    #[case(f64::INFINITY)]
781    fn buffered_floats_are_rejected(#[case] value: f64) {
782        assert!(decimal::deserialize(F64Deserializer::<Error>::new(value)).is_err());
783    }
784
785    #[rstest]
786    fn exact_numeric_serialization() {
787        for (token, expected, default_numeric) in cases() {
788            let text = serde_json::to_string(&Exact(expected));
789            let value = serde_json::to_value(Exact(expected));
790
791            let optional = serde_json::to_value(Optional {
792                value: Some(expected),
793            });
794
795            let supported = arbitrary_precision() || default_numeric;
796            assert_eq!(text.is_ok(), supported, "{token}: {text:?}");
797            assert_eq!(value.is_ok(), supported);
798            assert_eq!(optional.is_ok(), supported);
799
800            if supported {
801                let value = value.unwrap();
802                assert!(value.is_number());
803                assert_eq!(
804                    serde_json::from_str::<Raw>(&text.unwrap()).unwrap(),
805                    Raw(expected)
806                );
807                assert_eq!(
808                    serde_json::from_value::<Raw>(value.clone()).unwrap(),
809                    Raw(expected)
810                );
811                assert_eq!(decimal::parse(&value.to_string()).unwrap(), expected);
812                assert_eq!(optional.unwrap()["value"], value);
813            }
814        }
815
816        assert_eq!(
817            serde_json::to_value(Optional { value: None }).unwrap(),
818            serde_json::json!({"value": null})
819        );
820    }
821
822    #[rstest]
823    fn buffered_precision_boundary() {
824        let token = "0.12345678901234568";
825        let number = Number::from_str(token).unwrap();
826        let value = Value::Number(number.clone());
827        assert!(
828            decimal::deserialize(number.clone())
829                .unwrap_err()
830                .to_string()
831                .contains("floating point")
832        );
833        assert!(
834            serde_json::from_value::<Exact>(value.clone())
835                .unwrap_err()
836                .to_string()
837                .contains("floating point")
838        );
839        assert_eq!(
840            decimal::parse(&number.to_string()).unwrap(),
841            dec!(0.12345678901234568)
842        );
843        assert_eq!(
844            serde_json::from_value::<Raw>(value).unwrap(),
845            Raw(dec!(0.12345678901234568))
846        );
847
848        let original = "123456789.123456789";
849        let value: Value = serde_json::from_str(original).unwrap();
850        let buffered = serde_json::from_value::<Raw>(value).unwrap();
851
852        let expected = if arbitrary_precision() {
853            dec!(123456789.123456789)
854        } else {
855            dec!(123456789.12345679)
856        };
857
858        assert_eq!(buffered, Raw(expected));
859        assert_eq!(
860            serde_json::from_str::<Raw>(original).unwrap(),
861            Raw(dec!(123456789.123456789))
862        );
863    }
864
865    #[rstest]
866    fn permissive_buffered_overflow_returns_error() {
867        for token in [
868            "79228162514264337593543950336",
869            "-79228162514264337593543950336",
870        ] {
871            let mut direct = serde_json::Deserializer::from_str(token);
872            assert!(serialization::deserialize_decimal(&mut direct).is_err());
873            let value: Value = serde_json::from_str(token).unwrap();
874            assert!(serialization::deserialize_decimal(value.clone()).is_err());
875            assert!(serialization::deserialize_optional_decimal(value).is_err());
876        }
877    }
878
879    #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
880    struct StringDecimal(
881        #[serde(
882            deserialize_with = "decimal::deserialize",
883            serialize_with = "serialization::serialize_decimal_as_str"
884        )]
885        Decimal,
886    );
887
888    proptest! {
889        #[rstest]
890        fn prop_checked_integer_visitors(
891            signed in prop_oneof![Decimal::MIN.mantissa()..=Decimal::MAX.mantissa(), any::<i128>()],
892            unsigned in prop_oneof![0..=Decimal::MAX.mantissa().unsigned_abs(), any::<u128>()],
893        ) {
894            let expected = Decimal::from_str_exact(&signed.to_string()).ok();
895            prop_assert_eq!(decimal::deserialize(I128Deserializer::<Error>::new(signed)).ok(), expected);
896            prop_assert_eq!(serialization::deserialize_decimal(I128Deserializer::<Error>::new(signed)).ok(), expected);
897            let expected = Decimal::from_str_exact(&unsigned.to_string()).ok();
898            prop_assert_eq!(decimal::deserialize(U128Deserializer::<Error>::new(unsigned)).ok(), expected);
899            prop_assert_eq!(serialization::deserialize_decimal(U128Deserializer::<Error>::new(unsigned)).ok(), expected);
900        }
901
902        #[rstest]
903        fn prop_roundtrips_and_exponent_rescaling(
904            mantissa in Decimal::MIN.mantissa()..=Decimal::MAX.mantissa(),
905            scale in 0u32..=Decimal::MAX_SCALE,
906            zeros in 0usize..=35,
907        ) {
908            let expected = Decimal::try_from_i128_with_scale(mantissa, scale).unwrap();
909            let scientific = format!("{mantissa}{}e-{}", "0".repeat(zeros), scale as usize + zeros);
910            prop_assert_eq!(decimal::parse(&scientific).unwrap(), expected);
911            prop_assert_eq!(serde_json::from_str::<Raw>(&scientific).unwrap(), Raw(expected));
912            let mut plain = expected.to_string();
913            if !plain.contains('.') { plain.push('.'); }
914            plain.push_str(&"0".repeat(zeros));
915            prop_assert_eq!(decimal::parse(&plain).unwrap(), expected);
916            let string = serde_json::to_value(StringDecimal(expected)).unwrap();
917            prop_assert_eq!(serde_json::from_value::<StringDecimal>(string).unwrap(), StringDecimal(expected));
918            if let Ok(number) = serde_json::to_value(Exact(expected)) {
919                prop_assert_eq!(decimal::parse(&number.to_string()).unwrap(), expected);
920                prop_assert_eq!(serde_json::from_value::<Raw>(number).unwrap(), Raw(expected));
921            } else {
922                prop_assert!(!arbitrary_precision());
923            }
924        }
925
926        #[rstest]
927        fn prop_parse_never_panics(value in "\\PC{0,128}") {
928            let _ = decimal::parse(&value);
929        }
930    }
931}