Skip to main content

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