qubit_codec_text/error/charset_decode_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// =============================================================================
8use core::fmt;
9use std::error::Error;
10
11use crate::{
12 Charset,
13 CharsetDecodeErrorKind,
14};
15
16/// Error reported by a charset decoder.
17///
18/// The error always carries the charset, error kind, and input unit index at
19/// which the failure was detected. Errors that decode a raw numeric value, such
20/// as invalid UTF-32 units, carry that value through [`Self::kind`] and
21/// [`Self::value`]. Invalid-input errors may also carry a consumed-unit count
22/// so buffered decoders can make progress without an extra status wrapper.
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24pub struct CharsetDecodeError {
25 /// Charset being decoded when this error was detected.
26 charset: Charset,
27 /// Failure category describing the decoding error.
28 kind: CharsetDecodeErrorKind,
29 /// Input unit index at which decoding failure occurred.
30 index: usize,
31 /// Units that may be consumed after invalid input.
32 consumed: usize,
33}
34
35/// Result type returned by charset decoders.
36///
37/// # Type Parameters
38///
39/// - `T`: Successful value produced by a decoding operation.
40pub type CharsetDecodeResult<T> = Result<T, CharsetDecodeError>;
41
42impl CharsetDecodeError {
43 /// Creates a decoding error.
44 ///
45 /// # Parameters
46 ///
47 /// - `charset`: The charset being decoded.
48 /// - `kind`: The failure category.
49 /// - `index`: The input unit index where the failure was detected.
50 ///
51 /// # Returns
52 ///
53 /// Returns a decoding error carrying the supplied context.
54 #[inline(always)]
55 pub const fn new(
56 charset: Charset,
57 kind: CharsetDecodeErrorKind,
58 index: usize,
59 ) -> Self {
60 Self {
61 charset,
62 kind,
63 index,
64 consumed: 1,
65 }
66 }
67
68 /// Returns a copy of this error with invalid-input consumption context.
69 ///
70 /// # Parameters
71 ///
72 /// - `consumed`: Number of units that may be consumed to make progress.
73 ///
74 /// # Returns
75 ///
76 /// Returns this error carrying the supplied consumption count.
77 #[must_use]
78 #[inline(always)]
79 pub const fn with_consumed(self, consumed: usize) -> Self {
80 Self {
81 charset: self.charset,
82 kind: self.kind,
83 index: self.index,
84 consumed,
85 }
86 }
87
88 /// Returns the charset being decoded.
89 ///
90 /// # Returns
91 ///
92 /// Returns the stored [`Charset`].
93 #[inline(always)]
94 pub const fn charset(self) -> Charset {
95 self.charset
96 }
97
98 /// Returns the decoding error kind.
99 ///
100 /// # Returns
101 ///
102 /// Returns the stored [`CharsetDecodeErrorKind`].
103 #[inline(always)]
104 pub const fn kind(self) -> CharsetDecodeErrorKind {
105 self.kind
106 }
107
108 /// Returns the input unit index associated with this error.
109 ///
110 /// # Returns
111 ///
112 /// Returns the index at which the error was detected.
113 #[inline(always)]
114 pub const fn index(self) -> usize {
115 self.index
116 }
117
118 /// Returns required input units for this decoding error, if reported.
119 ///
120 /// # Returns
121 ///
122 /// Returns `Some(required)` for
123 /// [`CharsetDecodeErrorKind::IncompleteSequence`], otherwise `None`.
124 #[inline(always)]
125 pub const fn required(self) -> Option<usize> {
126 self.kind.required()
127 }
128
129 /// Returns available input units for this decoding error, if reported.
130 ///
131 /// # Returns
132 ///
133 /// Returns `Some(available)` for
134 /// [`CharsetDecodeErrorKind::IncompleteSequence`], otherwise `None`.
135 #[inline(always)]
136 pub const fn available(self) -> Option<usize> {
137 self.kind.available()
138 }
139
140 /// Returns input length for this decoding error, if reported.
141 ///
142 /// # Returns
143 ///
144 /// Returns `Some(input_len)` for
145 /// [`CharsetDecodeErrorKind::InvalidInputIndex`], otherwise `None`.
146 #[inline(always)]
147 pub const fn input_len(self) -> Option<usize> {
148 self.kind.input_len()
149 }
150
151 /// Returns output length for this decoding error, if reported.
152 ///
153 /// # Returns
154 ///
155 /// Returns `Some(output_len)` for
156 /// [`CharsetDecodeErrorKind::InvalidOutputIndex`], otherwise `None`.
157 #[inline(always)]
158 pub const fn output_len(self) -> Option<usize> {
159 self.kind.output_len()
160 }
161
162 /// Returns the raw value associated with this error.
163 ///
164 /// # Returns
165 ///
166 /// Returns `Some(value)` when the error kind carries a raw unit or code
167 /// point value, or `None` for kinds without an associated value.
168 #[inline(always)]
169 pub const fn value(self) -> Option<u32> {
170 self.kind.value()
171 }
172
173 /// Returns units that may be consumed after this invalid-input error.
174 ///
175 /// # Returns
176 ///
177 /// Returns `Some(consumed)` for malformed and invalid-code-point input, or
178 /// `None` for incomplete input and invalid caller indexes.
179 #[inline(always)]
180 pub const fn consumed(self) -> Option<usize> {
181 match self.kind {
182 CharsetDecodeErrorKind::MalformedSequence { .. }
183 | CharsetDecodeErrorKind::InvalidCodePoint { .. } => {
184 Some(self.consumed)
185 }
186 CharsetDecodeErrorKind::IncompleteSequence { .. }
187 | CharsetDecodeErrorKind::InvalidInputIndex { .. }
188 | CharsetDecodeErrorKind::InvalidOutputIndex { .. } => None,
189 }
190 }
191
192 /// Offsets this error by a base unit index.
193 ///
194 /// # Parameters
195 ///
196 /// - `base`: The base index to add to the stored index.
197 ///
198 /// # Returns
199 ///
200 /// Returns a copy of this error with its index shifted by `base`.
201 ///
202 /// If the shifted index cannot be represented, it is saturated to
203 /// [`usize::MAX`].
204 #[inline(always)]
205 pub const fn offset_by(self, base: usize) -> Self {
206 Self {
207 charset: self.charset,
208 kind: self.kind,
209 index: match self.index.checked_add(base) {
210 Some(index) => index,
211 None => usize::MAX,
212 },
213 consumed: self.consumed,
214 }
215 }
216}
217
218impl fmt::Display for CharsetDecodeError {
219 /// Formats this decoding error.
220 ///
221 /// # Parameters
222 ///
223 /// - `formatter`: The formatter receiving the diagnostic message.
224 ///
225 /// # Errors
226 ///
227 /// Returns any formatting error reported by `formatter`.
228 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
229 if let Some(value) = self.kind.value() {
230 write!(
231 formatter,
232 "{} decoding error at index {} for value 0x{:x}: {}",
233 self.charset, self.index, value, self.kind,
234 )
235 } else {
236 write!(
237 formatter,
238 "{} decoding error at index {}: {}",
239 self.charset, self.index, self.kind,
240 )
241 }
242 }
243}
244
245impl Error for CharsetDecodeError {}