Skip to main content

qubit_codec/transcode/
transcode_failure.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//! Framework-level failures reported by safe transcode APIs.
9
10use thiserror::Error;
11
12use super::capacity_error::CapacityError;
13use crate::Codec;
14use crate::codec::assert_unit_bounds;
15
16/// Framework-level failure reported by a transcode operation.
17///
18/// These failures are part of the safe public transcode API. They describe
19/// caller-supplied buffer ranges, capacity planning, complete-input shape, and
20/// stream lifecycle misuse that the transcode layer can detect without
21/// interpreting a concrete codec error.
22#[derive(Clone, Copy, Debug, Eq, Error, Hash, PartialEq)]
23#[non_exhaustive]
24pub enum TranscodeFailure {
25    /// The caller supplied an input index outside the input slice.
26    #[error("invalid input index {index} for input length {input_len}")]
27    InvalidInputIndex {
28        /// Invalid input index supplied by the caller.
29        index: usize,
30        /// Length of the input slice.
31        input_len: usize,
32    },
33
34    /// The caller supplied an output index outside the output slice.
35    #[error("invalid output index {index} for output length {output_len}")]
36    InvalidOutputIndex {
37        /// Invalid output index supplied by the caller.
38        index: usize,
39        /// Length of the output slice.
40        output_len: usize,
41    },
42
43    /// The caller supplied an output range extending beyond the output slice.
44    #[error("invalid output range at index {output_index} with length {range_len} for output length {output_len}")]
45    InvalidOutputRange {
46        /// Absolute output index where the range starts.
47        output_index: usize,
48        /// Length of the caller-supplied output range.
49        range_len: usize,
50        /// Length of the output slice.
51        output_len: usize,
52    },
53
54    /// The output slice cannot hold all required output.
55    #[error("insufficient output at index {output_index}: required {required} units, available {available}")]
56    InsufficientOutput {
57        /// Absolute output index where writing would start.
58        output_index: usize,
59        /// Output units required from `output_index`.
60        required: usize,
61        /// Output units available from `output_index`.
62        available: usize,
63    },
64
65    /// Output length arithmetic overflowed.
66    #[error("output length arithmetic overflow")]
67    OutputLengthOverflow,
68
69    /// The allocator could not reserve the requested output storage.
70    #[error("output allocation failed")]
71    AllocationFailed,
72
73    /// The complete input ended with an incomplete value.
74    #[error(
75        "incomplete input at index {input_index}: at least {required} units required to retry, {available} available"
76    )]
77    IncompleteInput {
78        /// Absolute input index where the incomplete value starts.
79        input_index: usize,
80        /// Current minimum total input units required from `input_index`
81        /// before retrying. A later retry may raise this lower bound.
82        required: usize,
83        /// Input units available from `input_index`.
84        available: usize,
85    },
86
87    /// The input contains exactly one decoded value plus trailing units.
88    #[error("trailing input after value: consumed {consumed} units, remaining {remaining}")]
89    TrailingInput {
90        /// Units consumed by the decoded value.
91        consumed: usize,
92        /// Extra units left after the decoded value.
93        remaining: usize,
94    },
95
96    /// A strict single-value decoder cannot expose codec lifecycle output.
97    #[error(
98        "strict single-value decoding does not support lifecycle output: reset bound {reset_bound}, finish bound {finish_bound}"
99    )]
100    UnsupportedDecodeLifecycleOutput {
101        /// Maximum values the codec may emit while resetting decode state.
102        reset_bound: usize,
103        /// Maximum values the codec may emit while finishing decode state.
104        finish_bound: usize,
105    },
106
107    /// `transcode` was called after the logical stream was finished.
108    #[error("transcode called after finish without an intervening reset")]
109    TranscodeAfterFinish,
110
111    /// `transcode` was called before the first successful reset.
112    #[error("transcode called before reset")]
113    TranscodeBeforeReset,
114
115    /// `finish` was called after the logical stream was already finished.
116    #[error("finish called twice without an intervening reset")]
117    FinishAfterFinish,
118
119    /// `finish` was called before the first successful reset.
120    #[error("finish called before reset")]
121    FinishBeforeReset,
122
123    /// Reset or finish failed after codec, hook, or buffered state may have
124    /// changed. Continuing could repeat side effects or observe inconsistent
125    /// state; a successful reset is required before further use.
126    #[error("transcoder lifecycle is poisoned; a successful reset is required")]
127    LifecyclePoisoned,
128}
129
130impl TranscodeFailure {
131    /// Creates an invalid-input-index error.
132    #[inline(always)]
133    #[must_use]
134    pub const fn invalid_input_index(index: usize, len: usize) -> Self {
135        Self::InvalidInputIndex { index, input_len: len }
136    }
137
138    /// Creates an invalid-output-index error.
139    #[inline(always)]
140    #[must_use]
141    pub const fn invalid_output_index(index: usize, len: usize) -> Self {
142        Self::InvalidOutputIndex { index, output_len: len }
143    }
144
145    /// Creates an invalid-output-range error.
146    #[inline(always)]
147    #[must_use]
148    pub const fn invalid_output_range(output_index: usize, range_len: usize, output_len: usize) -> Self {
149        Self::InvalidOutputRange {
150            output_index,
151            range_len,
152            output_len,
153        }
154    }
155
156    /// Creates an insufficient-output error.
157    #[inline(always)]
158    #[must_use]
159    pub const fn insufficient_output(output_index: usize, required: usize, available: usize) -> Self {
160        Self::InsufficientOutput {
161            output_index,
162            required,
163            available,
164        }
165    }
166
167    /// Creates an output-length-overflow error.
168    #[inline(always)]
169    #[must_use]
170    pub const fn output_length_overflow() -> Self {
171        Self::OutputLengthOverflow
172    }
173
174    /// Creates an output-allocation failure.
175    #[inline(always)]
176    #[must_use]
177    pub const fn allocation_failed() -> Self {
178        Self::AllocationFailed
179    }
180
181    /// Creates an incomplete-input error.
182    #[inline(always)]
183    #[must_use]
184    pub const fn incomplete_input(input_index: usize, required: usize, available: usize) -> Self {
185        Self::IncompleteInput {
186            input_index,
187            required,
188            available,
189        }
190    }
191
192    /// Creates a trailing-input error.
193    #[inline(always)]
194    #[must_use]
195    pub const fn trailing_input(consumed: usize, remaining: usize) -> Self {
196        Self::TrailingInput { consumed, remaining }
197    }
198
199    /// Creates an unsupported decode-lifecycle-output error.
200    #[inline(always)]
201    #[must_use]
202    pub const fn unsupported_decode_lifecycle_output(reset_bound: usize, finish_bound: usize) -> Self {
203        Self::UnsupportedDecodeLifecycleOutput {
204            reset_bound,
205            finish_bound,
206        }
207    }
208
209    /// Rejects lifecycle output for a strict single-value decode.
210    ///
211    /// # Type Parameters
212    ///
213    /// - `C`: Codec whose decode lifecycle bounds are checked.
214    ///
215    /// # Returns
216    ///
217    /// Returns `Ok(())` when both decode lifecycle output bounds are zero.
218    ///
219    /// # Errors
220    ///
221    /// Returns [`Self::UnsupportedDecodeLifecycleOutput`] when reset or finish
222    /// may emit values.
223    #[inline(always)]
224    pub(crate) fn ensure_no_decode_lifecycle_output<C>() -> Result<(), Self>
225    where
226        C: Codec,
227    {
228        assert_unit_bounds::<C>();
229        if C::MAX_DECODE_RESET_VALUES != 0 || C::MAX_DECODE_FINISH_VALUES != 0 {
230            return Err(Self::unsupported_decode_lifecycle_output(
231                C::MAX_DECODE_RESET_VALUES,
232                C::MAX_DECODE_FINISH_VALUES,
233            ));
234        }
235        Ok(())
236    }
237
238    /// Validates that `input_index` is within an input slice.
239    #[inline]
240    pub fn ensure_input_index(input_len: usize, input_index: usize) -> Result<(), Self> {
241        if input_index > input_len {
242            return Err(Self::invalid_input_index(input_index, input_len));
243        }
244        Ok(())
245    }
246
247    /// Validates that enough input units are available from `input_index`.
248    #[inline]
249    pub fn ensure_min_input(input_len: usize, input_index: usize, min_required: usize) -> Result<(), Self> {
250        Self::ensure_input_index(input_len, input_index)?;
251        let available = input_len - input_index;
252        if available < min_required {
253            return Err(Self::incomplete_input(input_index, min_required, available));
254        }
255        Ok(())
256    }
257
258    /// Validates a consumed input count and rejects trailing input.
259    ///
260    /// # Parameters
261    ///
262    /// - `consumed`: Number of input units consumed by the decoded value.
263    /// - `input_len`: Total number of input units supplied.
264    ///
265    /// # Returns
266    ///
267    /// Returns `Ok(())` when `consumed == input_len`.
268    ///
269    /// # Errors
270    ///
271    /// Returns [`TranscodeFailure::InvalidInputIndex`] when `consumed` exceeds
272    /// `input_len`, or [`TranscodeFailure::TrailingInput`] when unconsumed
273    /// input remains.
274    #[inline]
275    pub fn ensure_no_trailing_input(consumed: usize, input_len: usize) -> Result<(), Self> {
276        Self::ensure_input_index(input_len, consumed)?;
277        let remaining = input_len - consumed;
278        if remaining != 0 {
279            return Err(Self::trailing_input(consumed, remaining));
280        }
281        Ok(())
282    }
283
284    /// Validates that `output_index` is within an output slice.
285    #[inline]
286    pub fn ensure_output_index(output_len: usize, output_index: usize) -> Result<(), Self> {
287        if output_index > output_len {
288            return Err(Self::invalid_output_index(output_index, output_len));
289        }
290        Ok(())
291    }
292
293    /// Validates input and output start indices for a transcode call.
294    #[inline]
295    pub fn ensure_transcode_indices(
296        input_len: usize,
297        input_index: usize,
298        output_len: usize,
299        output_index: usize,
300    ) -> Result<(), Self> {
301        Self::ensure_input_index(input_len, input_index)?;
302        Self::ensure_output_index(output_len, output_index)
303    }
304
305    /// Validates that an output slice can hold one-shot finalization output.
306    #[inline]
307    pub fn ensure_output_capacity(output_len: usize, output_index: usize, required: usize) -> Result<(), Self> {
308        Self::ensure_output_index(output_len, output_index)?;
309        let available = output_len - output_index;
310        if available < required {
311            return Err(Self::insufficient_output(output_index, required, available));
312        }
313        Ok(())
314    }
315
316    /// Validates an indexed output range and its minimum writable capacity.
317    #[inline]
318    pub fn ensure_output_range(
319        output_len: usize,
320        output_index: usize,
321        range_len: usize,
322        required: usize,
323    ) -> Result<(), Self> {
324        Self::ensure_output_index(output_len, output_index)?;
325        let available = output_len - output_index;
326        if range_len > available {
327            return Err(Self::invalid_output_range(output_index, range_len, output_len));
328        }
329        if range_len < required {
330            return Err(Self::insufficient_output(output_index, required, range_len));
331        }
332        Ok(())
333    }
334}
335
336impl From<CapacityError> for TranscodeFailure {
337    /// Converts capacity planning errors into transcode framework failures.
338    #[inline(always)]
339    fn from(error: CapacityError) -> Self {
340        match error {
341            CapacityError::OutputLengthOverflow => Self::OutputLengthOverflow,
342        }
343    }
344}