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#[must_use]
73#[non_exhaustive]
74#[derive(Debug)]
75pub enum JsonValueEncodeError<R, Q>
76where
77 Q: ResourceQuantity,
78{
79 /// The caller's resource transaction rejected a value.
80 Budget(
81 /// The measured resource or quantity that caused admission to fail.
82 MeasuredBudgetError<R, Q>,
83 ),
84 /// The Serde value could not be represented as strict JSON.
85 Serialization(
86 /// The privacy-safe strict JSON serialization failure.
87 JsonSerializationError,
88 ),
89 /// A private RawValue or Number payload exceeded its configured UTF-8 text
90 /// limit.
91 PrivateTextLimitExceeded {
92 /// Maximum UTF-8 byte length allowed for one private text payload.
93 maximum: usize,
94 },
95}
96
97impl<R, Q> JsonValueEncodeError<R, Q>
98where
99 Q: ResourceQuantity,
100{
101 /// Returns the underlying serialization error when Serde projection failed.
102 ///
103 /// # Returns
104 ///
105 /// Returns `Some` for [`Self::Serialization`]. Returns `None` for
106 /// [`Self::Budget`] and [`Self::PrivateTextLimitExceeded`]; `None` means
107 /// the failure is not a serialization error, not that projection
108 /// succeeded.
109 #[inline]
110 pub const fn serialization_error(&self) -> Option<&JsonSerializationError> {
111 match self {
112 Self::Serialization(error) => Some(error),
113 Self::Budget(_) => None,
114 Self::PrivateTextLimitExceeded { .. } => None,
115 }
116 }
117
118 /// Returns the stable serialization failure kind, if applicable.
119 ///
120 /// # Returns
121 ///
122 /// Returns `Some` for [`Self::Serialization`]. Returns `None` for
123 /// [`Self::Budget`] and [`Self::PrivateTextLimitExceeded`]; `None` means
124 /// the failure has no serialization kind, not that projection succeeded.
125 #[inline]
126 pub const fn kind(&self) -> Option<JsonSerializationErrorKind> {
127 match self {
128 Self::Serialization(error) => Some(error.kind()),
129 Self::Budget(_) => None,
130 Self::PrivateTextLimitExceeded { .. } => None,
131 }
132 }
133
134 /// Returns the broad serialization category, if applicable.
135 ///
136 /// # Returns
137 ///
138 /// Returns `Some` for [`Self::Serialization`]. Returns `None` for
139 /// [`Self::Budget`] and [`Self::PrivateTextLimitExceeded`]; `None` means
140 /// the failure has no serialization category, not that projection
141 /// succeeded.
142 #[inline]
143 pub const fn category(&self) -> Option<JsonSerializationErrorCategory> {
144 match self {
145 Self::Serialization(error) => Some(error.category()),
146 Self::Budget(_) => None,
147 Self::PrivateTextLimitExceeded { .. } => None,
148 }
149 }
150
151 /// Reports whether the serialization failure concerns a JSON number.
152 #[must_use]
153 #[inline]
154 pub const fn is_number_error(&self) -> bool {
155 matches!(self, Self::Serialization(error) if error.is_number_error())
156 }
157
158 /// Reports whether the serialization failure concerns an object key.
159 #[must_use]
160 #[inline]
161 pub const fn is_map_key_error(&self) -> bool {
162 matches!(self, Self::Serialization(error) if error.is_map_key_error())
163 }
164
165 /// Reports whether the serialization failure concerns a raw JSON value.
166 #[must_use]
167 #[inline]
168 pub const fn is_raw_value_error(&self) -> bool {
169 matches!(self, Self::Serialization(error) if error.is_raw_value_error())
170 }
171
172 /// Reports whether Serde violated a serializer protocol contract.
173 #[must_use]
174 #[inline]
175 pub const fn is_serializer_contract_error(&self) -> bool {
176 matches!(self, Self::Serialization(error) if error.is_serializer_contract_error())
177 }
178
179 /// Returns the rejected integer signedness, if applicable.
180 #[inline]
181 pub const fn integer_signedness(&self) -> Option<JsonIntegerSignedness> {
182 match self {
183 Self::Serialization(error) => error.integer_signedness(),
184 Self::Budget(_) => None,
185 Self::PrivateTextLimitExceeded { .. } => None,
186 }
187 }
188
189 /// Returns the unsupported map-key category, if applicable.
190 #[inline]
191 pub const fn map_key_kind(&self) -> Option<JsonMapKeyKind> {
192 match self {
193 Self::Serialization(error) => error.map_key_kind(),
194 Self::Budget(_) => None,
195 Self::PrivateTextLimitExceeded { .. } => None,
196 }
197 }
198
199 /// Returns the collection whose length overflowed, if applicable.
200 #[inline]
201 pub const fn collection_kind(&self) -> Option<JsonCollectionKind> {
202 match self {
203 Self::Serialization(error) => error.collection_kind(),
204 Self::Budget(_) => None,
205 Self::PrivateTextLimitExceeded { .. } => None,
206 }
207 }
208
209 /// Returns the serializer protocol state failure, if applicable.
210 #[inline]
211 pub const fn serializer_state_error(&self) -> Option<JsonSerializerStateError> {
212 match self {
213 Self::Serialization(error) => error.serializer_state_error(),
214 Self::Budget(_) => None,
215 Self::PrivateTextLimitExceeded { .. } => None,
216 }
217 }
218}
219
220impl<R, Q> fmt::Display for JsonValueEncodeError<R, Q>
221where
222 Q: ResourceQuantity,
223{
224 /// Formats the failure without exposing private payload contents.
225 ///
226 /// # Parameters
227 ///
228 /// * `f` is the formatter that receives the privacy-safe failure message.
229 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230 match self {
231 Self::Budget(_) => f.write_str("JSON value budget rejected serialization"),
232 Self::Serialization(error) => write!(f, "{error}"),
233 Self::PrivateTextLimitExceeded { maximum } => {
234 write!(f, "private JSON text exceeded the {maximum}-byte limit")
235 }
236 }
237 }
238}
239
240impl<R, Q> std::error::Error for JsonValueEncodeError<R, Q>
241where
242 R: fmt::Debug + 'static,
243 Q: ResourceQuantity + 'static,
244{
245 /// Returns the underlying budget or serialization cause, when present.
246 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
247 match self {
248 Self::Budget(error) => Some(error),
249 Self::Serialization(error) => Some(error),
250 Self::PrivateTextLimitExceeded { .. } => None,
251 }
252 }
253}