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