Skip to main content

qubit_json/value/
json_value_encode_error.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//! Errors from budgeted JSON value projection.
9
10use std::fmt;
11
12use qubit_budget::MeasuredBudgetError;
13use qubit_budget::ResourceQuantity;
14
15use crate::encode::JsonCollectionKind;
16use crate::encode::JsonIntegerSignedness;
17use crate::encode::JsonMapKeyKind;
18use crate::encode::JsonSerializationError;
19use crate::encode::JsonSerializationErrorCategory;
20use crate::encode::JsonSerializationErrorKind;
21use crate::encode::JsonSerializerStateError;
22
23/// Failure while projecting a Serde value into a budgeted JSON tree.
24///
25/// The error distinguishes resource budget rejection, strict JSON
26/// serialization failure, and private text payloads that exceed their
27/// configured limit. Because this type is non-exhaustive, matches outside this
28/// crate must include a fallback arm. After any failure, discard the entire
29/// caller-owned resource transaction; do not treat its partial measurements as
30/// a successful projection.
31///
32/// `None` from a serialization query does not indicate success. It means that
33/// the error is not a serialization failure.
34///
35/// # Type Parameters
36///
37/// * `R` is the resource identifier type used by the budget.
38/// * `Q` is the resource quantity type and must implement [`ResourceQuantity`].
39///
40/// # Examples
41///
42/// ```
43/// use qubit_budget::json::JsonResource;
44/// use qubit_budget::json::JsonValueBudget;
45/// use qubit_budget::json::JsonValueLimits;
46/// use qubit_json::value::JsonValueEncodeError;
47/// use qubit_json::value::JsonValueEncoder;
48/// use serde_json::value::RawValue;
49///
50/// let limits = JsonValueLimits::<JsonResource, usize>::new();
51/// let mut budget = JsonValueBudget::new(limits);
52/// let mut transaction = budget.transaction();
53/// let raw = RawValue::from_string("null".to_owned()).unwrap();
54/// let error = JsonValueEncoder::new()
55///     .encode_in_transaction_with_private_text_limit(&raw, &mut transaction, 0)
56///     .unwrap_err();
57/// let classification = match &error {
58///     JsonValueEncodeError::Budget(_) => "budget",
59///     JsonValueEncodeError::Serialization(_) => "serialization",
60///     JsonValueEncodeError::PrivateTextLimitExceeded { maximum } => {
61///         assert_eq!(*maximum, 0);
62///         "private text limit"
63///     }
64///     _ => "other",
65/// };
66/// assert_eq!(classification, "private text limit");
67/// assert!(error.serialization_error().is_none());
68/// assert!(error.kind().is_none());
69/// assert!(error.category().is_none());
70/// drop(transaction);
71/// ```
72#[non_exhaustive]
73#[derive(Debug)]
74pub enum JsonValueEncodeError<R, Q>
75where
76    Q: ResourceQuantity,
77{
78    /// The caller's resource transaction rejected a value.
79    Budget(MeasuredBudgetError<R, Q>),
80    /// The Serde value could not be represented as strict JSON.
81    Serialization(JsonSerializationError),
82    /// A private RawValue or Number payload exceeded its configured UTF-8 text
83    /// limit.
84    PrivateTextLimitExceeded {
85        /// Maximum UTF-8 byte length allowed for one private text payload.
86        maximum: usize,
87    },
88}
89
90impl<R, Q> JsonValueEncodeError<R, Q>
91where
92    Q: ResourceQuantity,
93{
94    /// Returns the underlying serialization error when Serde projection failed.
95    ///
96    /// # Returns
97    ///
98    /// Returns `Some` for [`Self::Serialization`]. Returns `None` for
99    /// [`Self::Budget`] and [`Self::PrivateTextLimitExceeded`]; `None` means
100    /// the failure is not a serialization error, not that projection
101    /// succeeded.
102    pub const fn serialization_error(&self) -> Option<&JsonSerializationError> {
103        match self {
104            Self::Serialization(error) => Some(error),
105            Self::Budget(_) => None,
106            Self::PrivateTextLimitExceeded { .. } => None,
107        }
108    }
109
110    /// Returns the stable serialization failure kind, if applicable.
111    ///
112    /// # Returns
113    ///
114    /// Returns `Some` for [`Self::Serialization`]. Returns `None` for
115    /// [`Self::Budget`] and [`Self::PrivateTextLimitExceeded`]; `None` means
116    /// the failure has no serialization kind, not that projection succeeded.
117    pub const fn kind(&self) -> Option<JsonSerializationErrorKind> {
118        match self {
119            Self::Serialization(error) => Some(error.kind()),
120            Self::Budget(_) => None,
121            Self::PrivateTextLimitExceeded { .. } => None,
122        }
123    }
124
125    /// Returns the broad serialization category, if applicable.
126    ///
127    /// # Returns
128    ///
129    /// Returns `Some` for [`Self::Serialization`]. Returns `None` for
130    /// [`Self::Budget`] and [`Self::PrivateTextLimitExceeded`]; `None` means
131    /// the failure has no serialization category, not that projection
132    /// succeeded.
133    pub const fn category(&self) -> Option<JsonSerializationErrorCategory> {
134        match self {
135            Self::Serialization(error) => Some(error.category()),
136            Self::Budget(_) => None,
137            Self::PrivateTextLimitExceeded { .. } => None,
138        }
139    }
140
141    /// Reports whether the serialization failure concerns a JSON number.
142    pub const fn is_number_error(&self) -> bool {
143        matches!(self, Self::Serialization(error) if error.is_number_error())
144    }
145
146    /// Reports whether the serialization failure concerns an object key.
147    pub const fn is_map_key_error(&self) -> bool {
148        matches!(self, Self::Serialization(error) if error.is_map_key_error())
149    }
150
151    /// Reports whether the serialization failure concerns a raw JSON value.
152    pub const fn is_raw_value_error(&self) -> bool {
153        matches!(self, Self::Serialization(error) if error.is_raw_value_error())
154    }
155
156    /// Reports whether Serde violated a serializer protocol contract.
157    pub const fn is_serializer_contract_error(&self) -> bool {
158        matches!(self, Self::Serialization(error) if error.is_serializer_contract_error())
159    }
160
161    /// Returns the rejected integer signedness, if applicable.
162    pub const fn integer_signedness(&self) -> Option<JsonIntegerSignedness> {
163        match self {
164            Self::Serialization(error) => error.integer_signedness(),
165            Self::Budget(_) => None,
166            Self::PrivateTextLimitExceeded { .. } => None,
167        }
168    }
169
170    /// Returns the unsupported map-key category, if applicable.
171    pub const fn map_key_kind(&self) -> Option<JsonMapKeyKind> {
172        match self {
173            Self::Serialization(error) => error.map_key_kind(),
174            Self::Budget(_) => None,
175            Self::PrivateTextLimitExceeded { .. } => None,
176        }
177    }
178
179    /// Returns the collection whose length overflowed, if applicable.
180    pub const fn collection_kind(&self) -> Option<JsonCollectionKind> {
181        match self {
182            Self::Serialization(error) => error.collection_kind(),
183            Self::Budget(_) => None,
184            Self::PrivateTextLimitExceeded { .. } => None,
185        }
186    }
187
188    /// Returns the serializer protocol state failure, if applicable.
189    pub const fn serializer_state_error(&self) -> Option<JsonSerializerStateError> {
190        match self {
191            Self::Serialization(error) => error.serializer_state_error(),
192            Self::Budget(_) => None,
193            Self::PrivateTextLimitExceeded { .. } => None,
194        }
195    }
196}
197
198impl<R, Q> fmt::Display for JsonValueEncodeError<R, Q>
199where
200    Q: ResourceQuantity,
201{
202    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203        match self {
204            Self::Budget(_) => f.write_str("JSON value budget rejected serialization"),
205            Self::Serialization(error) => write!(f, "{error}"),
206            Self::PrivateTextLimitExceeded { maximum } => {
207                write!(f, "private JSON text exceeded the {maximum}-byte limit")
208            }
209        }
210    }
211}
212
213impl<R, Q> std::error::Error for JsonValueEncodeError<R, Q>
214where
215    R: fmt::Debug + 'static,
216    Q: ResourceQuantity + 'static,
217{
218    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
219        match self {
220            Self::Budget(error) => Some(error),
221            Self::Serialization(error) => Some(error),
222            Self::PrivateTextLimitExceeded { .. } => None,
223        }
224    }
225}