qubit_json/decode/json_number_policy.rs
1// =============================================================================
2// Copyright (c) 2026 Haixing Hu.
3//
4// SPDX-License-Identifier: Apache-2.0
5//
6// Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Number handling choices for validated decoding.
9
10/// Selects how a validated source number is represented in the output tree.
11///
12/// [`ExactSafeInteger`](Self::ExactSafeInteger) accepts only exact mathematical
13/// integer values in the inclusive interval
14/// `[-9007199254740991, 9007199254740991]`. It checks the decimal value
15/// directly, without rounding through `f64`, and projects an accepted value to
16/// `i64`. Thus, `1.00e2` is accepted as `100`, while fractional values and
17/// integral values outside that interval are rejected as distinct
18/// [`JsonNumberTokenError`](super::JsonNumberTokenError) categories.
19///
20/// After the scanner recognizes a valid number token, it calls the number
21/// policy before admitting that number's resource measurements. It then
22/// checks the selected decode model's range and applies the selected
23/// projection. A policy error returns immediately and can therefore precede
24/// a number-resource budget error.
25///
26/// Earlier input, depth, container, and validated-option checks can reject the
27/// document before the callback. A successful callback does not imply that
28/// resource admission or validation of the complete document succeeded; its
29/// side effects are not rolled back after a later failure. Selecting
30/// `ExactSafeInteger` does not bypass the decode model range check, so a
31/// spelling such as `1e400` can fail first as
32/// [`JsonValidatedDecodeError::Decode`](super::JsonValidatedDecodeError::Decode)
33/// with [`JsonSyntaxErrorReason::FloatOutOfRange`](super::JsonSyntaxErrorReason::FloatOutOfRange).
34///
35/// [`JsonNumberToken::exact_safe_integer`](super::JsonNumberToken::exact_safe_integer)
36/// exposes the same exact conversion for use inside a policy callback.
37///
38/// # Examples
39///
40/// ```
41/// use qubit_json::decode::{
42/// JsonDecoder, JsonNumberPolicy, JsonNumberTokenError,
43/// JsonSyntaxErrorReason, JsonValidatedDecodeError,
44/// JsonValidatedDecodeOptions,
45/// };
46/// use std::convert::Infallible;
47///
48/// let options = JsonValidatedDecodeOptions::builder().build();
49/// let decode = |text| {
50/// JsonDecoder::unlimited().decode_unique_validated_str(
51/// text,
52/// &options,
53/// |_, _| Ok::<_, Infallible>(JsonNumberPolicy::ExactSafeInteger),
54/// )
55/// };
56///
57/// let owner = decode("1.00e2").unwrap();
58/// assert_eq!(owner.as_value().as_i64(), Some(100));
59/// assert!(matches!(
60/// decode("0.1"),
61/// Err(JsonValidatedDecodeError::ExactSafeInteger {
62/// source: JsonNumberTokenError::Fractional,
63/// ..
64/// })
65/// ));
66/// assert!(matches!(
67/// decode("1e400"),
68/// Err(JsonValidatedDecodeError::Decode { error, .. })
69/// if error.syntax_error().map(|syntax| syntax.reason())
70/// == Some(JsonSyntaxErrorReason::FloatOutOfRange)
71/// ));
72/// ```
73#[non_exhaustive]
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
75pub enum JsonNumberPolicy {
76 /// Preserve the number according to the configured decode model.
77 Preserve,
78 /// Require an exact integer in JavaScript's safe integer range.
79 ///
80 /// The range is `[-9007199254740991, 9007199254740991]`. The decimal
81 /// mathematical value is checked without an intermediate `f64` rounding
82 /// step, then projected to `i64`. Fractional values produce
83 /// [`JsonNumberTokenError::Fractional`](super::JsonNumberTokenError::Fractional);
84 /// integral values outside the range produce
85 /// [`JsonNumberTokenError::OutOfSafeIntegerRange`](super::JsonNumberTokenError::OutOfSafeIntegerRange).
86 ExactSafeInteger,
87}