Skip to main content

qubit_codec/codec/
codec_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// =============================================================================
8//! Generic decode error used by codec adapters.
9
10use thiserror::Error;
11
12/// Error reported by codec-backed value and buffered decoder adapters.
13///
14/// The wrapped codec remains responsible for domain-specific decode failures.
15/// This type adds adapter-level failures that cannot be represented by the
16/// wrapped codec itself, such as a value decoder receiving too few units before
17/// it can safely call [`crate::Codec::decode`] or a buffered decoder
18/// receiving an invalid output start index.
19#[derive(Clone, Copy, Debug, Eq, Error, Hash, PartialEq)]
20pub enum CodecDecodeError<E> {
21    /// The wrapped codec reported a decode error.
22    #[error("codec decode error at input index {input_index}: {source}")]
23    Decode {
24        /// Error returned by the wrapped codec.
25        #[source]
26        source: E,
27        /// Absolute input index at which the adapter called the wrapped codec.
28        input_index: usize,
29    },
30
31    /// The adapter could not safely call the wrapped codec because input ended.
32    #[error(
33        "incomplete input at index {input_index}: required {required_total} units, available {available}"
34    )]
35    Incomplete {
36        /// Absolute input index where the incomplete value starts.
37        input_index: usize,
38        /// Total units required from `input_index`.
39        required_total: usize,
40        /// Units available from `input_index`.
41        available: usize,
42    },
43
44    /// A whole-value decode succeeded but left trailing input units.
45    #[error(
46        "trailing input after decoded value: consumed {consumed} units, remaining {remaining}"
47    )]
48    TrailingInput {
49        /// Units consumed by the decoded value.
50        consumed: usize,
51        /// Extra units left after the decoded value.
52        remaining: usize,
53    },
54
55    /// The caller supplied an input index outside the input slice.
56    #[error("invalid input index {index} for input length {len}")]
57    InvalidInputIndex {
58        /// Invalid input index supplied by the caller.
59        index: usize,
60        /// Length of the input slice.
61        len: usize,
62    },
63
64    /// The caller supplied an output index outside the output slice.
65    #[error("invalid output index {index} for output length {len}")]
66    InvalidOutputIndex {
67        /// Invalid output index supplied by the caller.
68        index: usize,
69        /// Length of the output slice.
70        len: usize,
71    },
72
73    /// The output slice cannot hold all output required by the adapter call.
74    #[error(
75        "insufficient output at index {output_index}: required {required} units, available {available}"
76    )]
77    InsufficientOutput {
78        /// Absolute output index where writing would start.
79        output_index: usize,
80        /// Output units required from `output_index`.
81        required: usize,
82        /// Output units available from `output_index`.
83        available: usize,
84    },
85}
86
87impl<E> CodecDecodeError<E> {
88    /// Creates an error wrapping a codec-specific decode error.
89    ///
90    /// # Parameters
91    ///
92    /// - `source`: Error returned by the wrapped codec.
93    /// - `input_index`: Absolute input index used for the codec call.
94    ///
95    /// # Returns
96    ///
97    /// Returns a codec decode error wrapper.
98    #[must_use]
99    #[inline(always)]
100    pub const fn decode(source: E, input_index: usize) -> Self {
101        Self::Decode {
102            source,
103            input_index,
104        }
105    }
106
107    /// Creates an adapter-level incomplete-input error.
108    ///
109    /// # Parameters
110    ///
111    /// - `input_index`: Absolute input index where the incomplete value starts.
112    /// - `required_total`: Total units required from `input_index`.
113    /// - `available`: Units available from `input_index`.
114    ///
115    /// # Returns
116    ///
117    /// Returns an incomplete-input error.
118    #[must_use]
119    #[inline(always)]
120    pub const fn incomplete(
121        input_index: usize,
122        required_total: usize,
123        available: usize,
124    ) -> Self {
125        Self::Incomplete {
126            input_index,
127            required_total,
128            available,
129        }
130    }
131
132    /// Creates a trailing-input error for whole-value decoding.
133    ///
134    /// # Parameters
135    ///
136    /// - `consumed`: Units consumed by the decoded value.
137    /// - `remaining`: Extra units left after the decoded value.
138    ///
139    /// # Returns
140    ///
141    /// Returns a trailing-input error.
142    #[must_use]
143    #[inline(always)]
144    pub const fn trailing_input(consumed: usize, remaining: usize) -> Self {
145        Self::TrailingInput {
146            consumed,
147            remaining,
148        }
149    }
150
151    /// Creates an invalid-input-index error.
152    ///
153    /// # Parameters
154    ///
155    /// - `index`: Invalid input index supplied by the caller.
156    /// - `len`: Length of the input slice.
157    ///
158    /// # Returns
159    ///
160    /// Returns an invalid-input-index error.
161    #[must_use]
162    #[inline(always)]
163    pub const fn invalid_input_index(index: usize, len: usize) -> Self {
164        Self::InvalidInputIndex { index, len }
165    }
166
167    /// Creates an invalid-output-index error.
168    ///
169    /// # Parameters
170    ///
171    /// - `index`: Invalid output index supplied by the caller.
172    /// - `len`: Length of the output slice.
173    ///
174    /// # Returns
175    ///
176    /// Returns an invalid-output-index error.
177    #[must_use]
178    #[inline(always)]
179    pub const fn invalid_output_index(index: usize, len: usize) -> Self {
180        Self::InvalidOutputIndex { index, len }
181    }
182
183    /// Creates an insufficient-output error.
184    #[must_use]
185    #[inline(always)]
186    pub const fn insufficient_output(
187        output_index: usize,
188        required: usize,
189        available: usize,
190    ) -> Self {
191        Self::InsufficientOutput {
192            output_index,
193            required,
194            available,
195        }
196    }
197
198    /// Returns whether this error indicates an incomplete input prefix.
199    ///
200    /// # Returns
201    ///
202    /// Returns `true` only for the [`Incomplete`](Self::Incomplete) variant.
203    #[must_use]
204    #[inline(always)]
205    pub const fn is_incomplete(&self) -> bool {
206        matches!(self, Self::Incomplete { .. })
207    }
208
209    /// Returns the additional input units needed to make progress.
210    ///
211    /// This is a convenience accessor over the [`Incomplete`](Self::Incomplete)
212    /// variant's fields. Streaming callers can use it to determine how many
213    /// more units they must buffer before retrying a decode.
214    ///
215    /// # Returns
216    ///
217    /// Returns `Some(needed)` for [`Incomplete`](Self::Incomplete) errors,
218    /// where `needed` is the strictly positive difference between the minimum
219    /// units required and those already available. Returns `None` for all
220    /// other variants.
221    #[must_use]
222    #[inline]
223    pub fn needed_additional(&self) -> Option<core::num::NonZeroUsize> {
224        match *self {
225            Self::Incomplete {
226                required_total,
227                available,
228                ..
229            } => {
230                let needed = required_total.saturating_sub(available);
231                core::num::NonZeroUsize::new(needed)
232            }
233            _ => None,
234        }
235    }
236
237    /// Validates that `input_index` is within an input slice.
238    ///
239    /// # Parameters
240    ///
241    /// - `input_len`: Length of the input slice.
242    /// - `input_index`: Input index supplied by the caller.
243    ///
244    /// # Returns
245    ///
246    /// Returns `Ok(())` when `input_index <= input_len`.
247    ///
248    /// # Errors
249    ///
250    /// Returns an invalid-input-index error when `input_index` is beyond the
251    /// slice.
252    #[inline]
253    pub fn ensure_input_index(
254        input_len: usize,
255        input_index: usize,
256    ) -> Result<(), Self> {
257        if input_index > input_len {
258            return Err(Self::invalid_input_index(input_index, input_len));
259        }
260        Ok(())
261    }
262
263    /// Validates that `output_index` is within an output slice.
264    ///
265    /// # Parameters
266    ///
267    /// - `output_len`: Length of the output slice.
268    /// - `output_index`: Output index supplied by the caller.
269    ///
270    /// # Returns
271    ///
272    /// Returns `Ok(())` when `output_index <= output_len`.
273    ///
274    /// # Errors
275    ///
276    /// Returns an invalid-output-index error when `output_index` is beyond the
277    /// slice.
278    #[inline]
279    pub fn ensure_output_index(
280        output_len: usize,
281        output_index: usize,
282    ) -> Result<(), Self> {
283        if output_index > output_len {
284            return Err(Self::invalid_output_index(output_index, output_len));
285        }
286        Ok(())
287    }
288
289    /// Validates that an output slice can hold required adapter output.
290    ///
291    /// # Parameters
292    ///
293    /// - `output_len`: Length of the output slice.
294    /// - `output_index`: Output index supplied by the caller.
295    /// - `required`: Output units required from `output_index`.
296    ///
297    /// # Returns
298    ///
299    /// Returns `Ok(())` when output capacity is sufficient.
300    ///
301    /// # Errors
302    ///
303    /// Returns an invalid-output-index error when `output_index` is beyond the
304    /// slice, or an insufficient-output error when fewer than `required` units
305    /// are writable from `output_index`.
306    #[inline]
307    pub fn ensure_output_capacity(
308        output_len: usize,
309        output_index: usize,
310        required: usize,
311    ) -> Result<(), Self> {
312        Self::ensure_output_index(output_len, output_index)?;
313        let available = output_len - output_index;
314        if available < required {
315            return Err(Self::insufficient_output(
316                output_index,
317                required,
318                available,
319            ));
320        }
321        Ok(())
322    }
323
324    /// Validates that enough input units are available from `input_index`.
325    ///
326    /// # Parameters
327    ///
328    /// - `input_len`: Length of the input slice.
329    /// - `input_index`: Absolute input index where reading starts.
330    /// - `min_required`: Minimum units required from `input_index`.
331    ///
332    /// # Returns
333    ///
334    /// Returns `Ok(())` when at least `min_required` units are available.
335    ///
336    /// # Errors
337    ///
338    /// Returns an incomplete-input error when fewer than `min_required` units
339    /// are available from `input_index`.
340    #[inline]
341    pub fn ensure_min_input(
342        input_len: usize,
343        input_index: usize,
344        min_required: usize,
345    ) -> Result<(), Self> {
346        let available = input_len.saturating_sub(input_index);
347        if available < min_required {
348            return Err(Self::incomplete(input_index, min_required, available));
349        }
350        Ok(())
351    }
352
353    /// Validates that decoding consumed the entire input slice.
354    ///
355    /// # Parameters
356    ///
357    /// - `consumed`: Units consumed by the decoded value.
358    /// - `total`: Total units in the input slice.
359    ///
360    /// # Returns
361    ///
362    /// Returns `Ok(())` when `consumed == total`.
363    ///
364    /// # Errors
365    ///
366    /// Returns a trailing-input error when extra units remain after the
367    /// decoded value.
368    #[inline]
369    pub fn ensure_no_trailing_input(
370        consumed: usize,
371        total: usize,
372    ) -> Result<(), Self> {
373        let remaining = total.saturating_sub(consumed);
374        if remaining != 0 {
375            return Err(Self::trailing_input(consumed, remaining));
376        }
377        Ok(())
378    }
379}