Skip to main content

qubit_codec/transcode/
transcode_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//! Errors reported by transcode engines and transcoder adapters.
9
10use core::fmt;
11
12/// Error reported by a transcode operation.
13///
14/// The enum keeps caller-contract failures in the framework layer and stores
15/// codec-, charset-, or policy-specific failures in [`TranscodeError::Domain`].
16///
17/// # Type Parameters
18///
19/// - `E`: Domain error reported by the concrete transcoder.
20#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
21pub enum TranscodeError<E> {
22    /// The caller supplied an input index beyond the input slice length.
23    InvalidInputIndex {
24        /// Invalid input index supplied by the caller.
25        index: usize,
26        /// Length of the input slice.
27        len: usize,
28    },
29    /// The caller supplied an output index beyond the output slice length.
30    InvalidOutputIndex {
31        /// Invalid output index supplied by the caller.
32        index: usize,
33        /// Length of the output slice.
34        len: usize,
35    },
36    /// The output slice cannot hold required one-shot reset or finish output.
37    InsufficientOutput {
38        /// Output index supplied by the caller.
39        output_index: usize,
40        /// Required writable output units.
41        required: usize,
42        /// Available writable output units.
43        available: usize,
44    },
45    /// Output length arithmetic overflowed.
46    OutputLengthOverflow,
47    /// Domain-specific codec, charset, or policy error.
48    Domain(E),
49}
50
51impl<E> TranscodeError<E> {
52    /// Creates a domain-specific transcode error.
53    ///
54    /// # Parameters
55    ///
56    /// - `error`: Domain error reported by the concrete transcoder.
57    ///
58    /// # Returns
59    ///
60    /// Returns a transcode error wrapping `error`.
61    #[inline(always)]
62    pub const fn domain(error: E) -> Self {
63        Self::Domain(error)
64    }
65
66    /// Creates an invalid-input-index error.
67    ///
68    /// # Parameters
69    ///
70    /// - `index`: Invalid input index supplied by the caller.
71    /// - `len`: Length of the input slice.
72    ///
73    /// # Returns
74    ///
75    /// Returns the invalid-input-index error.
76    #[inline(always)]
77    pub const fn invalid_input_index(index: usize, len: usize) -> Self {
78        Self::InvalidInputIndex { index, len }
79    }
80
81    /// Creates an invalid-output-index error.
82    ///
83    /// # Parameters
84    ///
85    /// - `index`: Invalid output index supplied by the caller.
86    /// - `len`: Length of the output slice.
87    ///
88    /// # Returns
89    ///
90    /// Returns the invalid-output-index error.
91    #[inline(always)]
92    pub const fn invalid_output_index(index: usize, len: usize) -> Self {
93        Self::InvalidOutputIndex { index, len }
94    }
95
96    /// Creates an insufficient-output error.
97    ///
98    /// # Parameters
99    ///
100    /// - `output_index`: Output index supplied by the caller.
101    /// - `required`: Output units required to finish in one call.
102    /// - `available`: Output units available from `output_index`.
103    ///
104    /// # Returns
105    ///
106    /// Returns the insufficient-output error.
107    #[inline(always)]
108    pub const fn insufficient_output(
109        output_index: usize,
110        required: usize,
111        available: usize,
112    ) -> Self {
113        Self::InsufficientOutput {
114            output_index,
115            required,
116            available,
117        }
118    }
119
120    /// Returns whether this error wraps a domain error.
121    ///
122    /// # Returns
123    ///
124    /// Returns `true` for [`TranscodeError::Domain`].
125    #[must_use]
126    #[inline(always)]
127    pub const fn is_domain(&self) -> bool {
128        matches!(self, Self::Domain(_))
129    }
130
131    /// Borrows the wrapped domain error.
132    ///
133    /// # Returns
134    ///
135    /// Returns `Some(error)` for [`TranscodeError::Domain`] and `None` for
136    /// framework contract errors.
137    #[must_use]
138    #[inline(always)]
139    pub const fn domain_ref(&self) -> Option<&E> {
140        match self {
141            Self::Domain(error) => Some(error),
142            _ => None,
143        }
144    }
145
146    /// Maps the wrapped domain error while preserving framework errors.
147    ///
148    /// # Type Parameters
149    ///
150    /// - `F`: Mapping function type.
151    /// - `T`: Target domain error type.
152    ///
153    /// # Parameters
154    ///
155    /// - `f`: Function applied to the wrapped domain error.
156    ///
157    /// # Returns
158    ///
159    /// Returns the mapped transcode error.
160    #[inline]
161    pub fn map_domain<F, T>(self, f: F) -> TranscodeError<T>
162    where
163        F: FnOnce(E) -> T,
164    {
165        match self {
166            Self::InvalidInputIndex { index, len } => {
167                TranscodeError::InvalidInputIndex { index, len }
168            }
169            Self::InvalidOutputIndex { index, len } => {
170                TranscodeError::InvalidOutputIndex { index, len }
171            }
172            Self::InsufficientOutput {
173                output_index,
174                required,
175                available,
176            } => TranscodeError::InsufficientOutput {
177                output_index,
178                required,
179                available,
180            },
181            Self::OutputLengthOverflow => TranscodeError::OutputLengthOverflow,
182            Self::Domain(error) => TranscodeError::Domain(f(error)),
183        }
184    }
185
186    /// Validates that `input_index` is within an input slice.
187    ///
188    /// # Parameters
189    ///
190    /// - `input_len`: Length of the input slice.
191    /// - `input_index`: Input index supplied by the caller.
192    ///
193    /// # Returns
194    ///
195    /// Returns `Ok(())` when `input_index <= input_len`.
196    ///
197    /// # Errors
198    ///
199    /// Returns an invalid-input-index error when `input_index` is beyond the
200    /// slice.
201    #[inline]
202    pub fn ensure_input_index(
203        input_len: usize,
204        input_index: usize,
205    ) -> Result<(), Self> {
206        if input_index > input_len {
207            return Err(Self::invalid_input_index(input_index, input_len));
208        }
209        Ok(())
210    }
211
212    /// Validates that `output_index` is within an output slice.
213    ///
214    /// # Parameters
215    ///
216    /// - `output_len`: Length of the output slice.
217    /// - `output_index`: Output index supplied by the caller.
218    ///
219    /// # Returns
220    ///
221    /// Returns `Ok(())` when `output_index <= output_len`.
222    ///
223    /// # Errors
224    ///
225    /// Returns an invalid-output-index error when `output_index` is beyond the
226    /// slice.
227    #[inline]
228    pub fn ensure_output_index(
229        output_len: usize,
230        output_index: usize,
231    ) -> Result<(), Self> {
232        if output_index > output_len {
233            return Err(Self::invalid_output_index(output_index, output_len));
234        }
235        Ok(())
236    }
237
238    /// Validates input and output start indices for a transcode call.
239    ///
240    /// # Parameters
241    ///
242    /// - `input_len`: Length of the input slice.
243    /// - `input_index`: Input index supplied by the caller.
244    /// - `output_len`: Length of the output slice.
245    /// - `output_index`: Output index supplied by the caller.
246    ///
247    /// # Returns
248    ///
249    /// Returns `Ok(())` when both indices are within their slices.
250    ///
251    /// # Errors
252    ///
253    /// Returns an invalid-input-index or invalid-output-index error when either
254    /// index is beyond its slice.
255    #[inline]
256    pub fn ensure_transcode_indices(
257        input_len: usize,
258        input_index: usize,
259        output_len: usize,
260        output_index: usize,
261    ) -> Result<(), Self> {
262        Self::ensure_input_index(input_len, input_index)?;
263        Self::ensure_output_index(output_len, output_index)
264    }
265
266    /// Validates that an output slice can hold one-shot finalization output.
267    ///
268    /// # Parameters
269    ///
270    /// - `output_len`: Length of the output slice.
271    /// - `output_index`: Output index supplied by the caller.
272    /// - `required`: Output units required to finish in one call.
273    ///
274    /// # Returns
275    ///
276    /// Returns `Ok(())` when output capacity is sufficient.
277    ///
278    /// # Errors
279    ///
280    /// Returns an invalid-output-index error when `output_index` is beyond the
281    /// slice, or an insufficient-output error when fewer than `required` units
282    /// are writable from `output_index`.
283    #[inline]
284    pub fn ensure_output_capacity(
285        output_len: usize,
286        output_index: usize,
287        required: usize,
288    ) -> Result<(), Self> {
289        Self::ensure_output_index(output_len, output_index)?;
290        let available = output_len - output_index;
291        if available < required {
292            return Err(Self::insufficient_output(
293                output_index,
294                required,
295                available,
296            ));
297        }
298        Ok(())
299    }
300
301    /// Validates an indexed output range and its minimum writable capacity.
302    ///
303    /// # Parameters
304    ///
305    /// - `output_len`: Length of the output slice.
306    /// - `output_index`: Output index supplied by the caller.
307    /// - `range_len`: Length of the writable range starting at `output_index`.
308    /// - `required`: Minimum output units required inside the range.
309    ///
310    /// # Returns
311    ///
312    /// Returns `Ok(())` when the range fits inside the slice and can hold
313    /// `required` units.
314    ///
315    /// # Errors
316    ///
317    /// Returns an invalid-output-index error when the range overflows or
318    /// extends beyond the slice, or an insufficient-output error when
319    /// `range_len` is smaller than `required`.
320    #[inline]
321    pub fn ensure_output_range(
322        output_len: usize,
323        output_index: usize,
324        range_len: usize,
325        required: usize,
326    ) -> Result<(), Self> {
327        Self::ensure_output_index(output_len, output_index)?;
328        if !qubit_io::UncheckedSlice::range_fits(
329            output_len,
330            output_index,
331            range_len,
332        ) {
333            return Err(Self::invalid_output_index(output_index, output_len));
334        }
335        if range_len < required {
336            return Err(Self::insufficient_output(
337                output_index,
338                required,
339                range_len,
340            ));
341        }
342        Ok(())
343    }
344}
345
346impl<E> From<E> for TranscodeError<E> {
347    /// Wraps a domain error in [`TranscodeError::Domain`].
348    ///
349    /// # Parameters
350    ///
351    /// - `error`: Domain error to wrap.
352    ///
353    /// # Returns
354    ///
355    /// Returns the wrapped transcode error.
356    #[inline(always)]
357    fn from(error: E) -> Self {
358        Self::Domain(error)
359    }
360}
361
362impl<E> fmt::Display for TranscodeError<E>
363where
364    E: fmt::Display,
365{
366    /// Formats the transcode error.
367    ///
368    /// # Parameters
369    ///
370    /// - `f`: Destination formatter.
371    ///
372    /// # Returns
373    ///
374    /// Returns the formatter result.
375    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
376        match self {
377            Self::InvalidInputIndex { index, len } => {
378                write!(f, "invalid input index {index}; input length is {len}")
379            }
380            Self::InvalidOutputIndex { index, len } => {
381                write!(
382                    f,
383                    "invalid output index {index}; output length is {len}"
384                )
385            }
386            Self::InsufficientOutput {
387                output_index,
388                required,
389                available,
390            } => write!(
391                f,
392                "insufficient output at index {output_index}; required {required}, available {available}"
393            ),
394            Self::OutputLengthOverflow => {
395                f.write_str("output length arithmetic overflow")
396            }
397            Self::Domain(error) => error.fmt(f),
398        }
399    }
400}
401
402impl<E> std::error::Error for TranscodeError<E>
403where
404    E: std::error::Error + 'static,
405{
406    /// Returns the source domain error, if any.
407    ///
408    /// # Returns
409    ///
410    /// Returns `Some(error)` for [`TranscodeError::Domain`] and `None` for
411    /// framework contract errors.
412    #[inline(always)]
413    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
414        match self {
415            Self::Domain(error) => Some(error),
416            _ => None,
417        }
418    }
419}