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}