Skip to main content

qubit_codec/transcode/
transcode_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//! Errors reported by transcode decoders.
9
10use core::num::NonZeroUsize;
11
12use thiserror::Error;
13
14use super::capacity_error::CapacityError;
15use super::transcode_domain_error::TranscodeDomainError;
16use super::transcode_failure::TranscodeFailure;
17use crate::Codec;
18use crate::DecodeFailure;
19
20/// Decode transcode error for a codec-backed decoder.
21///
22/// # Type Parameters
23///
24/// - `C`: Codec whose decode error becomes the domain failure.
25pub type TranscodeDecodeErrorOf<C> = TranscodeDecodeError<<C as Codec>::DecodeError>;
26
27/// Error reported by a decode-oriented transcode operation.
28///
29/// # Type Parameters
30///
31/// - `E`: Owned codec or policy failure preserved with transcode context.
32///
33/// # Examples
34///
35/// ```
36/// use qubit_codec::TranscodeDecodeError;
37///
38/// let error = TranscodeDecodeError::domain_main("invalid byte", 3);
39/// assert!(error.is_domain());
40/// assert_eq!(error.domain_ref(), Some(&"invalid byte"));
41/// assert!(error.failure_ref().is_none());
42/// ```
43#[derive(Clone, Debug, Eq, Error, Hash, PartialEq)]
44#[must_use]
45pub enum TranscodeDecodeError<E> {
46    /// Framework-level transcode failure.
47    #[error(transparent)]
48    Failure(
49        /// Framework contract, capacity, or incomplete-input failure.
50        #[from]
51        TranscodeFailure,
52    ),
53
54    /// Domain-specific codec, charset, or policy error.
55    #[error(transparent)]
56    Domain(
57        /// Codec failure retaining its phase and source position.
58        #[from]
59        TranscodeDomainError<E>,
60    ),
61}
62
63impl<E> TranscodeDecodeError<E> {
64    /// Creates a reset-phase domain-specific transcode error.
65    ///
66    /// # Parameters
67    ///
68    /// - `source`: Owned codec or policy failure.
69    ///
70    /// # Returns
71    ///
72    /// The domain failure tagged as reset.
73    #[inline]
74    pub const fn domain_reset(source: E) -> Self {
75        Self::Domain(TranscodeDomainError::reset(source))
76    }
77
78    /// Creates a main-phase domain-specific transcode error.
79    ///
80    /// # Parameters
81    ///
82    /// - `source`: Owned codec or policy failure.
83    /// - `input_index`: Absolute position of the failing input.
84    ///
85    /// # Returns
86    ///
87    /// The domain failure tagged with its main-phase input position.
88    #[inline]
89    pub const fn domain_main(source: E, input_index: usize) -> Self {
90        Self::Domain(TranscodeDomainError::main(source, input_index))
91    }
92
93    /// Creates a main-phase domain-specific transcode error with decode
94    /// consumption context.
95    ///
96    /// # Parameters
97    ///
98    /// - `source`: Owned codec or policy failure.
99    /// - `input_index`: Absolute position of the failing input.
100    /// - `input_consumed`: Some(non-zero span) when known, or None for an
101    ///   unknown span.
102    ///
103    /// # Returns
104    ///
105    /// The main-phase failure retaining the known or unknown consumed span.
106    #[inline]
107    pub const fn domain_main_with_consumed(
108        source: E,
109        input_index: usize,
110        input_consumed: Option<NonZeroUsize>,
111    ) -> Self {
112        Self::Domain(TranscodeDomainError::main_with_consumed(
113            source,
114            input_index,
115            input_consumed,
116        ))
117    }
118
119    /// Creates a finish-phase domain-specific transcode error.
120    ///
121    /// # Parameters
122    ///
123    /// - `source`: Owned codec or policy failure.
124    ///
125    /// # Returns
126    ///
127    /// The domain failure tagged as finish.
128    #[inline]
129    pub const fn domain_finish(source: E) -> Self {
130        Self::Domain(TranscodeDomainError::finish(source))
131    }
132
133    /// Converts a low-level decode failure into a decode transcode error.
134    ///
135    /// # Parameters
136    ///
137    /// - `failure`: Owned incomplete or invalid low-level failure.
138    /// - `input_index`: Absolute failing input position.
139    /// - `available`: Number of readable units at that position.
140    ///
141    /// # Returns
142    ///
143    /// A domain error when a codec source exists; otherwise an incomplete-input
144    /// framework failure.
145    #[inline]
146    pub fn from_decode_failure(failure: DecodeFailure<E>, input_index: usize, available: usize) -> Self {
147        match failure {
148            DecodeFailure::Incomplete {
149                source: Some(source), ..
150            } => Self::domain_main(source, input_index),
151            DecodeFailure::Incomplete { required_total, .. } => {
152                TranscodeFailure::incomplete_input(input_index, required_total.get(), available).into()
153            }
154            DecodeFailure::Invalid { source, consumed } => {
155                Self::domain_main_with_consumed(source, input_index, consumed)
156            }
157        }
158    }
159
160    /// Returns whether this error wraps a domain error.
161    ///
162    /// # Returns
163    ///
164    /// True for Domain and false for a framework Failure.
165    #[inline]
166    #[must_use]
167    pub const fn is_domain(&self) -> bool {
168        matches!(self, Self::Domain(_))
169    }
170
171    /// Returns the framework failure carried by this error.
172    ///
173    /// # Returns
174    ///
175    /// Some(borrowed failure) for Failure, or None for Domain.
176    #[inline]
177    pub const fn failure_ref(&self) -> Option<&TranscodeFailure> {
178        match self {
179            Self::Failure(failure) => Some(failure),
180            Self::Domain(_) => None,
181        }
182    }
183
184    /// Borrows the wrapped domain error and transcode context.
185    ///
186    /// # Returns
187    ///
188    /// Some(borrowed context) for Domain, or None for Failure.
189    #[inline]
190    pub const fn domain_error_ref(&self) -> Option<&TranscodeDomainError<E>> {
191        match self {
192            Self::Domain(error) => Some(error),
193            Self::Failure(_) => None,
194        }
195    }
196
197    /// Borrows the wrapped domain error.
198    ///
199    /// # Returns
200    ///
201    /// Some(borrowed source) for Domain, or None for Failure.
202    #[inline]
203    pub const fn domain_ref(&self) -> Option<&E> {
204        match self {
205            Self::Domain(error) => Some(error.source()),
206            Self::Failure(_) => None,
207        }
208    }
209
210    /// Maps the wrapped domain error while preserving framework failures.
211    ///
212    /// # Type Parameters
213    ///
214    /// - `F`: One-shot mapping closure, invoked only for a domain failure.
215    /// - `T`: Replacement domain error type.
216    ///
217    /// # Parameters
218    ///
219    /// - `f`: Consumes the domain source, if present; may perform
220    ///   caller-defined side effects. Framework failures do not invoke it.
221    ///
222    /// # Returns
223    ///
224    /// The mapped error retaining phase and input context, or the original
225    /// framework failure unchanged.
226    #[inline]
227    pub fn map_domain<F, T>(self, f: F) -> TranscodeDecodeError<T>
228    where
229        F: FnOnce(E) -> T,
230    {
231        match self {
232            Self::Failure(failure) => TranscodeDecodeError::Failure(failure),
233            Self::Domain(error) => TranscodeDecodeError::Domain(error.map_source(f)),
234        }
235    }
236}
237
238impl<E> From<CapacityError> for TranscodeDecodeError<E> {
239    /// Converts capacity planning errors into transcode framework errors.
240    ///
241    /// # Parameters
242    ///
243    /// - `error`: Owned capacity planning failure.
244    ///
245    /// # Returns
246    ///
247    /// The corresponding framework failure wrapped in this decode error.
248    #[inline]
249    fn from(error: CapacityError) -> Self {
250        TranscodeFailure::from(error).into()
251    }
252}