Skip to main content

qubit_json/encode/
json_serialization_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//! Defines stable, privacy-safe JSON serialization failures.
9
10use std::fmt::Display;
11
12use serde::ser::Error as SerError;
13use thiserror::Error;
14
15use super::JsonCollectionKind;
16use super::JsonIntegerSignedness;
17use super::JsonMapKeyKind;
18use super::JsonSerializationErrorCategory;
19use super::JsonSerializationErrorKind;
20use super::JsonSerializerStateError;
21
22/// Privacy-safe failure produced while serializing a value as strict JSON.
23///
24/// The error exposes exact stable kinds and broad handling categories without
25/// retaining input values, object keys, or arbitrary third-party diagnostics.
26///
27/// # Examples
28///
29/// ```
30/// use qubit_json::encode::JsonIntegerSignedness;
31/// use qubit_json::encode::JsonSerializationErrorKind;
32/// use qubit_json::value::JsonValueEncoder;
33///
34/// let error = JsonValueEncoder::new()
35///     .encode(&u128::MAX)
36///     .expect_err("wide integer must be rejected");
37/// assert_eq!(
38///     error.kind(),
39///     JsonSerializationErrorKind::IntegerOutOfRange {
40///         signedness: JsonIntegerSignedness::Unsigned,
41///     },
42/// );
43/// ```
44#[must_use]
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Error)]
46#[error("{kind}")]
47pub struct JsonSerializationError {
48    /// Exact privacy-safe classification.
49    kind: JsonSerializationErrorKind,
50}
51
52impl JsonSerializationError {
53    /// Creates an error from its stable public kind.
54    ///
55    /// # Parameters
56    ///
57    /// * `kind` - Exact privacy-safe serialization failure.
58    ///
59    /// # Returns
60    ///
61    /// A serialization error retaining only `kind`.
62    #[inline(always)]
63    pub const fn new(kind: JsonSerializationErrorKind) -> Self {
64        Self { kind }
65    }
66
67    /// Returns the exact stable failure classification.
68    ///
69    /// # Returns
70    ///
71    /// The precise serialization failure kind retained by this error.
72    #[inline(always)]
73    pub const fn kind(&self) -> JsonSerializationErrorKind {
74        self.kind
75    }
76
77    /// Returns the broad downstream handling category.
78    ///
79    /// # Returns
80    ///
81    /// The broad category that determines which class of serialization
82    /// failure occurred.
83    #[must_use]
84    pub const fn category(&self) -> JsonSerializationErrorCategory {
85        match self.kind {
86            JsonSerializationErrorKind::IntegerOutOfRange { .. }
87            | JsonSerializationErrorKind::NonFiniteFloat
88            | JsonSerializationErrorKind::InvalidNumberRepresentation => {
89                JsonSerializationErrorCategory::Number
90            }
91            JsonSerializationErrorKind::UnsupportedMapKey { .. }
92            | JsonSerializationErrorKind::DuplicateObjectKey => {
93                JsonSerializationErrorCategory::ObjectKey
94            }
95            JsonSerializationErrorKind::InvalidRawValue => JsonSerializationErrorCategory::RawValue,
96            JsonSerializationErrorKind::CollectionLengthOverflow { .. } => {
97                JsonSerializationErrorCategory::Capacity
98            }
99            JsonSerializationErrorKind::InvalidSerializerState { .. }
100            | JsonSerializationErrorKind::DisplayFormattingFailed => {
101                JsonSerializationErrorCategory::SerializerContract
102            }
103            JsonSerializationErrorKind::CustomSerialization => {
104                JsonSerializationErrorCategory::Custom
105            }
106        }
107    }
108
109    /// Reports whether this failure belongs to the strict number contract.
110    ///
111    /// # Returns
112    ///
113    /// `true` when the failure concerns a JSON number; otherwise, `false`.
114    #[must_use]
115    #[inline(always)]
116    pub const fn is_number_error(&self) -> bool {
117        matches!(self.category(), JsonSerializationErrorCategory::Number)
118    }
119
120    /// Reports whether this failure concerns JSON object-key representation.
121    ///
122    /// # Returns
123    ///
124    /// `true` when the failure concerns an object key; otherwise, `false`.
125    #[must_use]
126    #[inline(always)]
127    pub const fn is_map_key_error(&self) -> bool {
128        matches!(self.category(), JsonSerializationErrorCategory::ObjectKey)
129    }
130
131    /// Reports whether this failure concerns a RawValue payload.
132    ///
133    /// # Returns
134    ///
135    /// `true` when the failure concerns a `RawValue` payload; otherwise,
136    /// `false`.
137    #[must_use]
138    #[inline(always)]
139    pub const fn is_raw_value_error(&self) -> bool {
140        matches!(self.category(), JsonSerializationErrorCategory::RawValue)
141    }
142
143    /// Reports whether a hand-written serializer violated a protocol contract.
144    ///
145    /// # Returns
146    ///
147    /// `true` when a serializer or display implementation violated the
148    /// compound protocol; otherwise, `false`.
149    #[must_use]
150    #[inline(always)]
151    pub const fn is_serializer_contract_error(&self) -> bool {
152        matches!(
153            self.category(),
154            JsonSerializationErrorCategory::SerializerContract
155        )
156    }
157
158    /// Returns the signedness of an out-of-range integer, when applicable.
159    ///
160    /// # Returns
161    ///
162    /// `Some(signedness)` for an out-of-range integer, or `None` for every
163    /// other serialization failure.
164    #[inline(always)]
165    pub const fn integer_signedness(&self) -> Option<JsonIntegerSignedness> {
166        match self.kind {
167            JsonSerializationErrorKind::IntegerOutOfRange { signedness } => Some(signedness),
168            _ => None,
169        }
170    }
171
172    /// Returns the rejected map-key shape, when applicable.
173    ///
174    /// # Returns
175    ///
176    /// `Some(kind)` for an unsupported map key, or `None` for every other
177    /// serialization failure.
178    #[inline(always)]
179    pub const fn map_key_kind(&self) -> Option<JsonMapKeyKind> {
180        match self.kind {
181            JsonSerializationErrorKind::UnsupportedMapKey { kind } => Some(kind),
182            _ => None,
183        }
184    }
185
186    /// Returns the collection whose count overflowed, when applicable.
187    ///
188    /// # Returns
189    ///
190    /// `Some(kind)` for a collection-length overflow, or `None` for every
191    /// other serialization failure.
192    #[inline(always)]
193    pub const fn collection_kind(&self) -> Option<JsonCollectionKind> {
194        match self.kind {
195            JsonSerializationErrorKind::CollectionLengthOverflow { kind } => Some(kind),
196            _ => None,
197        }
198    }
199
200    /// Returns the exact invalid serializer state, when applicable.
201    ///
202    /// # Returns
203    ///
204    /// `Some(reason)` for an invalid serializer state, or `None` for every
205    /// other serialization failure.
206    #[inline(always)]
207    pub const fn serializer_state_error(&self) -> Option<JsonSerializerStateError> {
208        match self.kind {
209            JsonSerializationErrorKind::InvalidSerializerState { reason } => Some(reason),
210            _ => None,
211        }
212    }
213}
214
215impl SerError for JsonSerializationError {
216    /// Converts arbitrary custom serializer text into one opaque, stable kind.
217    ///
218    /// # Type Parameters
219    ///
220    /// * `T` - Displayable custom text supplied by the serializer.
221    ///
222    /// # Parameters
223    ///
224    /// * `_message` - Custom text, which is intentionally discarded to avoid
225    ///   retaining arbitrary diagnostics.
226    ///
227    /// # Returns
228    ///
229    /// An error classified as
230    /// [`JsonSerializationErrorKind::CustomSerialization`].
231    fn custom<T>(_message: T) -> Self
232    where
233        T: Display,
234    {
235        Self::new(JsonSerializationErrorKind::CustomSerialization)
236    }
237}