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 #[inline(always)]
72 pub const fn kind(&self) -> JsonSerializationErrorKind {
73 self.kind
74 }
75
76 /// Returns the broad downstream handling category.
77 ///
78 /// # Returns
79 ///
80 /// The broad category that determines which class of serialization
81 /// failure occurred.
82 #[must_use]
83 pub const fn category(&self) -> JsonSerializationErrorCategory {
84 match self.kind {
85 JsonSerializationErrorKind::IntegerOutOfRange { .. }
86 | JsonSerializationErrorKind::NonFiniteFloat
87 | JsonSerializationErrorKind::InvalidNumberRepresentation => JsonSerializationErrorCategory::Number,
88 JsonSerializationErrorKind::UnsupportedMapKey { .. } | JsonSerializationErrorKind::DuplicateObjectKey => {
89 JsonSerializationErrorCategory::ObjectKey
90 }
91 JsonSerializationErrorKind::InvalidRawValue => JsonSerializationErrorCategory::RawValue,
92 JsonSerializationErrorKind::CollectionLengthOverflow { .. } => JsonSerializationErrorCategory::Capacity,
93 JsonSerializationErrorKind::InvalidSerializerState { .. }
94 | JsonSerializationErrorKind::DisplayFormattingFailed => JsonSerializationErrorCategory::SerializerContract,
95 JsonSerializationErrorKind::CustomSerialization => JsonSerializationErrorCategory::Custom,
96 }
97 }
98
99 /// Reports whether this failure belongs to the strict number contract.
100 ///
101 /// # Returns
102 ///
103 /// `true` when the failure concerns a JSON number; otherwise, `false`.
104 #[must_use]
105 #[inline(always)]
106 pub const fn is_number_error(&self) -> bool {
107 matches!(self.category(), JsonSerializationErrorCategory::Number)
108 }
109
110 /// Reports whether this failure concerns JSON object-key representation.
111 ///
112 /// # Returns
113 ///
114 /// `true` when the failure concerns an object key; otherwise, `false`.
115 #[must_use]
116 #[inline(always)]
117 pub const fn is_map_key_error(&self) -> bool {
118 matches!(self.category(), JsonSerializationErrorCategory::ObjectKey)
119 }
120
121 /// Reports whether this failure concerns a RawValue payload.
122 ///
123 /// # Returns
124 ///
125 /// `true` when the failure concerns a `RawValue` payload; otherwise,
126 /// `false`.
127 #[must_use]
128 #[inline(always)]
129 pub const fn is_raw_value_error(&self) -> bool {
130 matches!(self.category(), JsonSerializationErrorCategory::RawValue)
131 }
132
133 /// Reports whether a hand-written serializer violated a protocol contract.
134 ///
135 /// # Returns
136 ///
137 /// `true` when a serializer or display implementation violated the
138 /// compound protocol; otherwise, `false`.
139 #[must_use]
140 #[inline(always)]
141 pub const fn is_serializer_contract_error(&self) -> bool {
142 matches!(self.category(), JsonSerializationErrorCategory::SerializerContract)
143 }
144
145 /// Returns the signedness of an out-of-range integer, when applicable.
146 ///
147 /// # Returns
148 ///
149 /// `Some(signedness)` for an out-of-range integer, or `None` for every
150 /// other serialization failure.
151 #[must_use]
152 #[inline(always)]
153 pub const fn integer_signedness(&self) -> Option<JsonIntegerSignedness> {
154 match self.kind {
155 JsonSerializationErrorKind::IntegerOutOfRange { signedness } => Some(signedness),
156 _ => None,
157 }
158 }
159
160 /// Returns the rejected map-key shape, when applicable.
161 ///
162 /// # Returns
163 ///
164 /// `Some(kind)` for an unsupported map key, or `None` for every other
165 /// serialization failure.
166 #[must_use]
167 #[inline(always)]
168 pub const fn map_key_kind(&self) -> Option<JsonMapKeyKind> {
169 match self.kind {
170 JsonSerializationErrorKind::UnsupportedMapKey { kind } => Some(kind),
171 _ => None,
172 }
173 }
174
175 /// Returns the collection whose count overflowed, when applicable.
176 ///
177 /// # Returns
178 ///
179 /// `Some(kind)` for a collection-length overflow, or `None` for every
180 /// other serialization failure.
181 #[must_use]
182 #[inline(always)]
183 pub const fn collection_kind(&self) -> Option<JsonCollectionKind> {
184 match self.kind {
185 JsonSerializationErrorKind::CollectionLengthOverflow { kind } => Some(kind),
186 _ => None,
187 }
188 }
189
190 /// Returns the exact invalid serializer state, when applicable.
191 ///
192 /// # Returns
193 ///
194 /// `Some(reason)` for an invalid serializer state, or `None` for every
195 /// other serialization failure.
196 #[must_use]
197 #[inline(always)]
198 pub const fn serializer_state_error(&self) -> Option<JsonSerializerStateError> {
199 match self.kind {
200 JsonSerializationErrorKind::InvalidSerializerState { reason } => Some(reason),
201 _ => None,
202 }
203 }
204}
205
206impl serde::ser::Error for JsonSerializationError {
207 /// Converts arbitrary custom serializer text into one opaque, stable kind.
208 fn custom<T>(_message: T) -> Self
209 where
210 T: Display,
211 {
212 Self::new(JsonSerializationErrorKind::CustomSerialization)
213 }
214}