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::{
15    capacity_error::CapacityError,
16    transcode_domain_error::TranscodeDomainError,
17    transcode_failure::TranscodeFailure,
18};
19use crate::{
20    Codec,
21    DecodeFailure,
22};
23
24/// Decode transcode error for a codec-backed decoder.
25pub type TranscodeDecodeErrorOf<C> =
26    TranscodeDecodeError<<C as Codec>::DecodeError>;
27
28/// Error reported by a decode-oriented transcode operation.
29#[derive(Clone, Debug, Eq, Error, Hash, PartialEq)]
30pub enum TranscodeDecodeError<E> {
31    /// Framework-level transcode failure.
32    #[error(transparent)]
33    Failure(#[from] TranscodeFailure),
34
35    /// Domain-specific codec, charset, or policy error.
36    #[error(transparent)]
37    Domain(#[from] TranscodeDomainError<E>),
38}
39
40impl<E> TranscodeDecodeError<E> {
41    /// Creates a reset-phase domain-specific transcode error.
42    #[inline(always)]
43    pub const fn domain_reset(source: E) -> Self {
44        Self::Domain(TranscodeDomainError::reset(source))
45    }
46
47    /// Creates a main-phase domain-specific transcode error.
48    #[inline(always)]
49    pub const fn domain_main(source: E, input_index: usize) -> Self {
50        Self::Domain(TranscodeDomainError::main(source, input_index))
51    }
52
53    /// Creates a main-phase domain-specific transcode error with decode
54    /// consumption context.
55    #[inline(always)]
56    pub const fn domain_main_with_consumed(
57        source: E,
58        input_index: usize,
59        input_consumed: Option<NonZeroUsize>,
60    ) -> Self {
61        Self::Domain(TranscodeDomainError::main_with_consumed(
62            source,
63            input_index,
64            input_consumed,
65        ))
66    }
67
68    /// Creates a finish-phase domain-specific transcode error.
69    #[inline(always)]
70    pub const fn domain_finish(source: E) -> Self {
71        Self::Domain(TranscodeDomainError::finish(source))
72    }
73
74    /// Converts a low-level decode failure into a decode transcode error.
75    #[inline]
76    #[must_use]
77    pub fn from_decode_failure(
78        failure: DecodeFailure<E>,
79        input_index: usize,
80        available: usize,
81    ) -> Self {
82        match failure {
83            DecodeFailure::Incomplete {
84                source: Some(source),
85                ..
86            } => Self::domain_main(source, input_index),
87            DecodeFailure::Incomplete { required_total, .. } => {
88                TranscodeFailure::incomplete_input(
89                    input_index,
90                    required_total.get(),
91                    available,
92                )
93                .into()
94            }
95            DecodeFailure::Invalid { source, consumed } => {
96                Self::domain_main_with_consumed(source, input_index, consumed)
97            }
98        }
99    }
100
101    /// Returns whether this error wraps a domain error.
102    #[inline(always)]
103    #[must_use]
104    pub const fn is_domain(&self) -> bool {
105        matches!(self, Self::Domain(_))
106    }
107
108    /// Returns the framework failure carried by this error.
109    #[inline(always)]
110    #[must_use]
111    pub const fn failure_ref(&self) -> Option<&TranscodeFailure> {
112        match self {
113            Self::Failure(failure) => Some(failure),
114            Self::Domain(_) => None,
115        }
116    }
117
118    /// Borrows the wrapped domain error and transcode context.
119    #[inline(always)]
120    #[must_use]
121    pub const fn domain_error_ref(&self) -> Option<&TranscodeDomainError<E>> {
122        match self {
123            Self::Domain(error) => Some(error),
124            Self::Failure(_) => None,
125        }
126    }
127
128    /// Borrows the wrapped domain error.
129    #[must_use]
130    pub const fn domain_ref(&self) -> Option<&E> {
131        match self {
132            Self::Domain(error) => Some(error.source()),
133            Self::Failure(_) => None,
134        }
135    }
136
137    /// Maps the wrapped domain error while preserving framework failures.
138    #[inline]
139    pub fn map_domain<F, T>(self, f: F) -> TranscodeDecodeError<T>
140    where
141        F: FnOnce(E) -> T,
142    {
143        match self {
144            Self::Failure(failure) => TranscodeDecodeError::Failure(failure),
145            Self::Domain(error) => {
146                TranscodeDecodeError::Domain(error.map_source(f))
147            }
148        }
149    }
150}
151
152impl<E> From<CapacityError> for TranscodeDecodeError<E> {
153    /// Converts capacity planning errors into transcode framework errors.
154    #[inline(always)]
155    fn from(error: CapacityError) -> Self {
156        TranscodeFailure::from(error).into()
157    }
158}