Skip to main content

qubit_codec/transcode/
transcode_convert_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 converters.
9
10use thiserror::Error;
11
12use super::capacity_error::CapacityError;
13use super::transcode_decode_error::TranscodeDecodeError;
14use super::transcode_domain_error::TranscodeDomainError;
15use super::transcode_encode_error::TranscodeEncodeError;
16use super::transcode_failure::TranscodeFailure;
17use crate::Codec;
18
19/// Convert transcode error for a codec-backed converter.
20pub type TranscodeConvertErrorOf<D, E> =
21    TranscodeConvertError<<D as Codec>::DecodeError, <E as Codec>::EncodeError, <D as Codec>::Value>;
22
23/// Error reported by a unit-to-unit transcode conversion.
24#[derive(Clone, Debug, Eq, Error, Hash, PartialEq)]
25pub enum TranscodeConvertError<DE, EE, V> {
26    /// Framework-level transcode failure.
27    #[error(transparent)]
28    Failure(#[from] TranscodeFailure),
29
30    /// Source-side domain error.
31    #[error("decode side failed: {0}")]
32    DecodeDomain(#[source] TranscodeDomainError<DE>),
33
34    /// Target-side domain error.
35    #[error("encode side failed: {0}")]
36    EncodeDomain(#[source] TranscodeDomainError<EE>),
37
38    /// The decoded intermediate value cannot be encoded by the target codec and
39    /// policy.
40    #[error("unencodable value at input index {input_index}")]
41    Unencodable {
42        /// Absolute source input index of the value being encoded.
43        input_index: usize,
44        /// Decoded intermediate value, when available.
45        value: Option<V>,
46    },
47}
48
49impl<DE, EE, V> TranscodeConvertError<DE, EE, V> {
50    /// Creates a source reset-phase domain-specific converter error.
51    #[inline(always)]
52    pub const fn decode_domain_reset(source: DE) -> Self {
53        Self::DecodeDomain(TranscodeDomainError::reset(source))
54    }
55
56    /// Creates a source main-phase domain-specific converter error.
57    #[inline(always)]
58    pub const fn decode_domain_main(source: DE, input_index: usize) -> Self {
59        Self::DecodeDomain(TranscodeDomainError::main(source, input_index))
60    }
61
62    /// Creates a source main-phase converter error with decode consumption
63    /// context.
64    #[inline(always)]
65    pub const fn decode_domain_main_with_consumed(
66        source: DE,
67        input_index: usize,
68        input_consumed: Option<core::num::NonZeroUsize>,
69    ) -> Self {
70        Self::DecodeDomain(TranscodeDomainError::main_with_consumed(
71            source,
72            input_index,
73            input_consumed,
74        ))
75    }
76
77    /// Creates a source finish-phase domain-specific converter error.
78    #[inline(always)]
79    pub const fn decode_domain_finish(source: DE) -> Self {
80        Self::DecodeDomain(TranscodeDomainError::finish(source))
81    }
82
83    /// Creates a target reset-phase domain-specific converter error.
84    #[inline(always)]
85    pub const fn encode_domain_reset(source: EE) -> Self {
86        Self::EncodeDomain(TranscodeDomainError::reset(source))
87    }
88
89    /// Creates a target main-phase domain-specific converter error.
90    #[inline(always)]
91    pub const fn encode_domain_main(source: EE, input_index: usize) -> Self {
92        Self::EncodeDomain(TranscodeDomainError::main(source, input_index))
93    }
94
95    /// Creates a target finish-phase domain-specific converter error.
96    #[inline(always)]
97    pub const fn encode_domain_finish(source: EE) -> Self {
98        Self::EncodeDomain(TranscodeDomainError::finish(source))
99    }
100
101    /// Creates an unencodable intermediate-value error with value context.
102    #[inline(always)]
103    #[must_use]
104    pub fn unencodable(input_index: usize, value: V) -> Self {
105        Self::Unencodable {
106            input_index,
107            value: Some(value),
108        }
109    }
110
111    /// Creates an unencodable intermediate-value error without value context.
112    #[inline(always)]
113    #[must_use]
114    pub const fn unencodable_without_context(input_index: usize) -> Self {
115        Self::Unencodable {
116            input_index,
117            value: None,
118        }
119    }
120
121    /// Returns the framework failure carried by this error.
122    #[inline(always)]
123    #[must_use]
124    pub const fn failure_ref(&self) -> Option<&TranscodeFailure> {
125        match self {
126            Self::Failure(failure) => Some(failure),
127            Self::DecodeDomain(_) | Self::EncodeDomain(_) | Self::Unencodable { .. } => None,
128        }
129    }
130
131    /// Borrows the unencodable value context carried by this error.
132    #[inline(always)]
133    #[must_use]
134    pub const fn unencodable_ref(&self) -> Option<(usize, Option<&V>)> {
135        match self {
136            Self::Unencodable { input_index, value } => Some((*input_index, value.as_ref())),
137            Self::Failure(_) | Self::DecodeDomain(_) | Self::EncodeDomain(_) => None,
138        }
139    }
140
141    /// Maps the source domain error while preserving other errors.
142    #[inline]
143    pub fn map_decode_domain<F, T>(self, f: F) -> TranscodeConvertError<T, EE, V>
144    where
145        F: FnOnce(DE) -> T,
146    {
147        match self {
148            Self::Failure(failure) => TranscodeConvertError::Failure(failure),
149            Self::DecodeDomain(error) => TranscodeConvertError::DecodeDomain(error.map_source(f)),
150            Self::EncodeDomain(error) => TranscodeConvertError::EncodeDomain(error),
151            Self::Unencodable { input_index, value } => TranscodeConvertError::Unencodable { input_index, value },
152        }
153    }
154
155    /// Maps the target domain error while preserving other errors.
156    #[inline]
157    pub fn map_encode_domain<F, T>(self, f: F) -> TranscodeConvertError<DE, T, V>
158    where
159        F: FnOnce(EE) -> T,
160    {
161        match self {
162            Self::Failure(failure) => TranscodeConvertError::Failure(failure),
163            Self::DecodeDomain(error) => TranscodeConvertError::DecodeDomain(error),
164            Self::EncodeDomain(error) => TranscodeConvertError::EncodeDomain(error.map_source(f)),
165            Self::Unencodable { input_index, value } => TranscodeConvertError::Unencodable { input_index, value },
166        }
167    }
168
169    /// Maps value context carried by unencodable-value errors.
170    #[inline]
171    pub fn map_value<F, W>(self, f: F) -> TranscodeConvertError<DE, EE, W>
172    where
173        F: FnOnce(V) -> W,
174    {
175        match self {
176            Self::Failure(failure) => TranscodeConvertError::Failure(failure),
177            Self::DecodeDomain(error) => TranscodeConvertError::DecodeDomain(error),
178            Self::EncodeDomain(error) => TranscodeConvertError::EncodeDomain(error),
179            Self::Unencodable { input_index, value } => TranscodeConvertError::Unencodable {
180                input_index,
181                value: value.map(f),
182            },
183        }
184    }
185
186    /// Converts an encode error into a converter error while adding fallback
187    /// value context to unencodable errors that lack it.
188    #[inline]
189    pub fn from_encode_error_with_value(error: TranscodeEncodeError<EE, V>, fallback_value: V) -> Self {
190        match error {
191            TranscodeEncodeError::Unencodable {
192                input_index,
193                value: None,
194            } => Self::Unencodable {
195                input_index,
196                value: Some(fallback_value),
197            },
198            other => Self::from(other),
199        }
200    }
201}
202
203impl<DE, EE, V> From<TranscodeDecodeError<DE>> for TranscodeConvertError<DE, EE, V> {
204    /// Converts a source-side decode error into a converter error.
205    #[inline]
206    fn from(error: TranscodeDecodeError<DE>) -> Self {
207        match error {
208            TranscodeDecodeError::Failure(failure) => Self::Failure(failure),
209            TranscodeDecodeError::Domain(error) => Self::DecodeDomain(error),
210        }
211    }
212}
213
214impl<DE, EE, V> From<TranscodeEncodeError<EE, V>> for TranscodeConvertError<DE, EE, V> {
215    /// Converts a target-side encode error into a converter error.
216    #[inline]
217    fn from(error: TranscodeEncodeError<EE, V>) -> Self {
218        match error {
219            TranscodeEncodeError::Failure(failure) => Self::Failure(failure),
220            TranscodeEncodeError::Unencodable { input_index, value } => Self::Unencodable { input_index, value },
221            TranscodeEncodeError::Domain(error) => Self::EncodeDomain(error),
222        }
223    }
224}
225
226impl<DE, EE, V> From<CapacityError> for TranscodeConvertError<DE, EE, V> {
227    /// Converts capacity planning errors into transcode framework errors.
228    #[inline(always)]
229    fn from(error: CapacityError) -> Self {
230        TranscodeFailure::from(error).into()
231    }
232}