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#[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}