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}