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