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 #[must_use]
178 pub const fn failure_ref(&self) -> Option<&TranscodeFailure> {
179 match self {
180 Self::Failure(failure) => Some(failure),
181 Self::Domain(_) => None,
182 }
183 }
184
185 /// Borrows the wrapped domain error and transcode context.
186 ///
187 /// # Returns
188 ///
189 /// Some(borrowed context) for Domain, or None for Failure.
190 #[inline]
191 #[must_use]
192 pub const fn domain_error_ref(&self) -> Option<&TranscodeDomainError<E>> {
193 match self {
194 Self::Domain(error) => Some(error),
195 Self::Failure(_) => None,
196 }
197 }
198
199 /// Borrows the wrapped domain error.
200 ///
201 /// # Returns
202 ///
203 /// Some(borrowed source) for Domain, or None for Failure.
204 #[inline]
205 #[must_use]
206 pub const fn domain_ref(&self) -> Option<&E> {
207 match self {
208 Self::Domain(error) => Some(error.source()),
209 Self::Failure(_) => None,
210 }
211 }
212
213 /// Maps the wrapped domain error while preserving framework failures.
214 ///
215 /// # Type Parameters
216 ///
217 /// - `F`: One-shot mapping closure, invoked only for a domain failure.
218 /// - `T`: Replacement domain error type.
219 ///
220 /// # Parameters
221 ///
222 /// - `f`: Consumes the domain source, if present; may perform
223 /// caller-defined side effects. Framework failures do not invoke it.
224 ///
225 /// # Returns
226 ///
227 /// The mapped error retaining phase and input context, or the original
228 /// framework failure unchanged.
229 #[inline]
230 pub fn map_domain<F, T>(self, f: F) -> TranscodeDecodeError<T>
231 where
232 F: FnOnce(E) -> T,
233 {
234 match self {
235 Self::Failure(failure) => TranscodeDecodeError::Failure(failure),
236 Self::Domain(error) => TranscodeDecodeError::Domain(error.map_source(f)),
237 }
238 }
239}
240
241impl<E> From<CapacityError> for TranscodeDecodeError<E> {
242 /// Converts capacity planning errors into transcode framework errors.
243 ///
244 /// # Parameters
245 ///
246 /// - `error`: Owned capacity planning failure.
247 ///
248 /// # Returns
249 ///
250 /// The corresponding framework failure wrapped in this decode error.
251 #[inline]
252 fn from(error: CapacityError) -> Self {
253 TranscodeFailure::from(error).into()
254 }
255}