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}