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 thiserror::Error;
11
12use super::capacity_error::CapacityError;
13
14/// Error reported by a transcode operation.
15///
16/// Buffer contract failures are framework errors owned by the transcode layer
17/// and are represented directly by this enum. Codec-, charset-, or
18/// policy-specific failures are carried as domain errors.
19///
20/// # Type Parameters
21///
22/// - `E`: Domain error reported by the concrete transcoder.
23#[derive(Clone, Copy, Debug, Eq, Error, Hash, PartialEq)]
24pub enum TranscodeError<E> {
25    /// The caller supplied an input index outside the input slice.
26    #[error("invalid input index {index} for input length {len}")]
27    InvalidInputIndex {
28        /// Invalid input index supplied by the caller.
29        index: usize,
30        /// Length of the input slice.
31        len: usize,
32    },
33
34    /// The caller supplied an output index outside the output slice.
35    #[error("invalid output index {index} for output length {len}")]
36    InvalidOutputIndex {
37        /// Invalid output index supplied by the caller.
38        index: usize,
39        /// Length of the output slice.
40        len: usize,
41    },
42
43    /// The output slice cannot hold all required output.
44    #[error(
45        "insufficient output at index {output_index}: required {required} units, available {available}"
46    )]
47    InsufficientOutput {
48        /// Absolute output index where writing would start.
49        output_index: usize,
50        /// Output units required from `output_index`.
51        required: usize,
52        /// Output units available from `output_index`.
53        available: usize,
54    },
55
56    /// Output length arithmetic overflowed.
57    #[error("output length arithmetic overflow")]
58    OutputLengthOverflow,
59
60    /// Domain-specific codec, charset, or policy error.
61    #[error("{0}")]
62    Domain(#[source] E),
63}
64
65impl<E> TranscodeError<E> {
66    /// Creates a domain-specific transcode error.
67    ///
68    /// # Parameters
69    ///
70    /// - `error`: Domain error reported by the concrete transcoder.
71    ///
72    /// # Returns
73    ///
74    /// Returns a transcode error wrapping `error`.
75    pub const fn domain(error: E) -> Self {
76        Self::Domain(error)
77    }
78
79    /// Creates an invalid-input-index error.
80    #[must_use]
81    pub const fn invalid_input_index(index: usize, len: usize) -> Self {
82        Self::InvalidInputIndex { index, len }
83    }
84
85    /// Creates an invalid-output-index error.
86    #[must_use]
87    pub const fn invalid_output_index(index: usize, len: usize) -> Self {
88        Self::InvalidOutputIndex { index, len }
89    }
90
91    /// Creates an insufficient-output error.
92    #[must_use]
93    pub const fn insufficient_output(
94        output_index: usize,
95        required: usize,
96        available: usize,
97    ) -> Self {
98        Self::InsufficientOutput {
99            output_index,
100            required,
101            available,
102        }
103    }
104
105    /// Creates an output-length-overflow error.
106    #[must_use]
107    pub const fn output_length_overflow() -> Self {
108        Self::OutputLengthOverflow
109    }
110
111    /// Returns whether this error wraps a domain error.
112    ///
113    /// # Returns
114    ///
115    /// Returns `true` for [`TranscodeError::Domain`].
116    #[must_use]
117    pub const fn is_domain(&self) -> bool {
118        matches!(self, Self::Domain(_))
119    }
120
121    /// Borrows the wrapped domain error.
122    ///
123    /// # Returns
124    ///
125    /// Returns `Some(error)` for [`TranscodeError::Domain`] and `None` for
126    /// buffer contract errors.
127    #[must_use]
128    pub const fn domain_ref(&self) -> Option<&E> {
129        match self {
130            Self::Domain(error) => Some(error),
131            Self::InvalidInputIndex { .. }
132            | Self::InvalidOutputIndex { .. }
133            | Self::InsufficientOutput { .. }
134            | Self::OutputLengthOverflow => None,
135        }
136    }
137
138    /// Maps the wrapped domain error while preserving buffer contract errors.
139    ///
140    /// # Type Parameters
141    ///
142    /// - `F`: Mapping function type.
143    /// - `T`: Target domain error type.
144    ///
145    /// # Parameters
146    ///
147    /// - `f`: Function applied to the wrapped domain error.
148    ///
149    /// # Returns
150    ///
151    /// Returns the mapped transcode error.
152    #[inline]
153    pub fn map_domain<F, T>(self, f: F) -> TranscodeError<T>
154    where
155        F: FnOnce(E) -> T,
156    {
157        match self {
158            Self::InvalidInputIndex { index, len } => {
159                TranscodeError::InvalidInputIndex { index, len }
160            }
161            Self::InvalidOutputIndex { index, len } => {
162                TranscodeError::InvalidOutputIndex { index, len }
163            }
164            Self::InsufficientOutput {
165                output_index,
166                required,
167                available,
168            } => TranscodeError::InsufficientOutput {
169                output_index,
170                required,
171                available,
172            },
173            Self::OutputLengthOverflow => TranscodeError::OutputLengthOverflow,
174            Self::Domain(error) => TranscodeError::Domain(f(error)),
175        }
176    }
177
178    /// Validates that `input_index` is within an input slice.
179    #[inline]
180    pub fn ensure_input_index(
181        input_len: usize,
182        input_index: usize,
183    ) -> Result<(), Self> {
184        if input_index > input_len {
185            return Err(Self::invalid_input_index(input_index, input_len));
186        }
187        Ok(())
188    }
189
190    /// Validates that `output_index` is within an output slice.
191    #[inline]
192    pub fn ensure_output_index(
193        output_len: usize,
194        output_index: usize,
195    ) -> Result<(), Self> {
196        if output_index > output_len {
197            return Err(Self::invalid_output_index(output_index, output_len));
198        }
199        Ok(())
200    }
201
202    /// Validates input and output start indices for a transcode call.
203    #[inline]
204    pub fn ensure_transcode_indices(
205        input_len: usize,
206        input_index: usize,
207        output_len: usize,
208        output_index: usize,
209    ) -> Result<(), Self> {
210        Self::ensure_input_index(input_len, input_index)?;
211        Self::ensure_output_index(output_len, output_index)
212    }
213
214    /// Validates that an output slice can hold one-shot finalization output.
215    #[inline]
216    pub fn ensure_output_capacity(
217        output_len: usize,
218        output_index: usize,
219        required: usize,
220    ) -> Result<(), Self> {
221        Self::ensure_output_index(output_len, output_index)?;
222        let available = output_len - output_index;
223        if available < required {
224            return Err(Self::insufficient_output(
225                output_index,
226                required,
227                available,
228            ));
229        }
230        Ok(())
231    }
232
233    /// Validates an indexed output range and its minimum writable capacity.
234    #[inline]
235    pub fn ensure_output_range(
236        output_len: usize,
237        output_index: usize,
238        range_len: usize,
239        required: usize,
240    ) -> Result<(), Self> {
241        Self::ensure_output_index(output_len, output_index)?;
242        if !qubit_io::UncheckedSlice::range_fits(
243            output_len,
244            output_index,
245            range_len,
246        ) {
247            return Err(Self::invalid_output_index(output_index, output_len));
248        }
249        if range_len < required {
250            return Err(Self::insufficient_output(
251                output_index,
252                required,
253                range_len,
254            ));
255        }
256        Ok(())
257    }
258}
259
260impl<E> From<CapacityError> for TranscodeError<E> {
261    /// Converts capacity planning errors into transcode framework errors.
262    #[inline(always)]
263    fn from(error: CapacityError) -> Self {
264        match error {
265            CapacityError::OutputLengthOverflow => Self::OutputLengthOverflow,
266        }
267    }
268}