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