Skip to main content

qubit_codec/transcode/
transcode_domain_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//! Domain-specific transcode errors with codec phase context.
9
10use core::num::NonZeroUsize;
11
12use thiserror::Error;
13
14/// Domain-specific codec, charset, or policy error with transcode context.
15///
16/// Reset and finish failures are lifecycle failures and therefore cannot carry
17/// an input index. Main-phase failures always carry the absolute input index
18/// where the domain error occurred.
19///
20/// # Type Parameters
21///
22/// - `E`: Domain error reported by the concrete codec, hook, or facade.
23#[derive(Clone, Copy, Debug, Eq, Error, Hash, PartialEq)]
24pub enum TranscodeDomainError<E> {
25    /// Domain error reported while resetting codec or hook state.
26    #[error("codec reset error: {source}")]
27    Reset {
28        /// Domain error returned by the codec or policy facade.
29        #[source]
30        source: E,
31    },
32
33    /// Domain error reported while processing input.
34    #[error("codec main error at input index {input_index}: {source}")]
35    Main {
36        /// Domain error returned by the codec or policy facade.
37        #[source]
38        source: E,
39        /// Absolute input index where the error occurred.
40        input_index: usize,
41        /// Invalid input units consumed by the codec-domain error, when known.
42        input_consumed: Option<NonZeroUsize>,
43    },
44
45    /// Domain error reported while finishing codec or hook state.
46    #[error("codec finish error: {source}")]
47    Finish {
48        /// Domain error returned by the codec or policy facade.
49        #[source]
50        source: E,
51    },
52}
53
54impl<E> TranscodeDomainError<E> {
55    /// Creates a reset-phase domain error.
56    ///
57    /// # Parameters
58    ///
59    /// - `source`: Domain error returned by reset handling.
60    ///
61    /// # Returns
62    ///
63    /// Returns a reset-phase domain error.
64    #[inline(always)]
65    #[must_use]
66    pub const fn reset(source: E) -> Self {
67        Self::Reset { source }
68    }
69
70    /// Creates a main-phase domain error.
71    ///
72    /// # Parameters
73    ///
74    /// - `source`: Domain error returned by value processing.
75    /// - `input_index`: Absolute input index where the error occurred.
76    ///
77    /// # Returns
78    ///
79    /// Returns a main-phase domain error.
80    #[inline(always)]
81    #[must_use]
82    pub const fn main(source: E, input_index: usize) -> Self {
83        Self::Main {
84            source,
85            input_index,
86            input_consumed: None,
87        }
88    }
89
90    /// Creates a main-phase domain error with decode consumption context.
91    ///
92    /// # Parameters
93    ///
94    /// - `source`: Domain error returned by value processing.
95    /// - `input_index`: Absolute input index where the error occurred.
96    /// - `input_consumed`: Invalid input units consumed, when known.
97    ///
98    /// # Returns
99    ///
100    /// Returns a main-phase domain error with consumption context.
101    #[inline(always)]
102    #[must_use]
103    pub const fn main_with_consumed(source: E, input_index: usize, input_consumed: Option<NonZeroUsize>) -> Self {
104        Self::Main {
105            source,
106            input_index,
107            input_consumed,
108        }
109    }
110
111    /// Creates a finish-phase domain error.
112    ///
113    /// # Parameters
114    ///
115    /// - `source`: Domain error returned by finish handling.
116    ///
117    /// # Returns
118    ///
119    /// Returns a finish-phase domain error.
120    #[inline(always)]
121    #[must_use]
122    pub const fn finish(source: E) -> Self {
123        Self::Finish { source }
124    }
125
126    /// Returns the wrapped source error.
127    ///
128    /// # Returns
129    ///
130    /// Returns a shared reference to the domain error.
131    #[inline(always)]
132    #[must_use]
133    pub const fn source(&self) -> &E {
134        match self {
135            Self::Reset { source } | Self::Main { source, .. } | Self::Finish { source } => source,
136        }
137    }
138
139    /// Consumes this error and returns the wrapped source error.
140    ///
141    /// # Returns
142    ///
143    /// Returns the domain error.
144    #[inline(always)]
145    #[must_use]
146    pub fn into_source(self) -> E {
147        match self {
148            Self::Reset { source } | Self::Main { source, .. } | Self::Finish { source } => source,
149        }
150    }
151
152    /// Returns the absolute input index associated with the error.
153    ///
154    /// # Returns
155    ///
156    /// Returns `Some(index)` for main-phase errors and `None` for reset or
157    /// finish errors.
158    #[inline(always)]
159    #[must_use]
160    pub const fn input_index(&self) -> Option<usize> {
161        match self {
162            Self::Main { input_index, .. } => Some(*input_index),
163            Self::Reset { .. } | Self::Finish { .. } => None,
164        }
165    }
166
167    /// Returns invalid input consumption associated with the error.
168    ///
169    /// # Returns
170    ///
171    /// Returns `Some(consumed)` when a main-phase decode error reports the
172    /// invalid input width. Returns `None` otherwise.
173    #[inline(always)]
174    #[must_use]
175    pub const fn input_consumed(&self) -> Option<NonZeroUsize> {
176        match self {
177            Self::Main { input_consumed, .. } => *input_consumed,
178            Self::Reset { .. } | Self::Finish { .. } => None,
179        }
180    }
181
182    /// Maps the wrapped source error while preserving phase context.
183    ///
184    /// # Type Parameters
185    ///
186    /// - `T`: Target source error type.
187    /// - `F`: Mapping function type.
188    ///
189    /// # Parameters
190    ///
191    /// - `f`: Function applied to the wrapped source error.
192    ///
193    /// # Returns
194    ///
195    /// Returns the mapped domain error.
196    #[inline]
197    pub fn map_source<T, F>(self, f: F) -> TranscodeDomainError<T>
198    where
199        F: FnOnce(E) -> T,
200    {
201        match self {
202            Self::Reset { source } => TranscodeDomainError::Reset { source: f(source) },
203            Self::Main {
204                source,
205                input_index,
206                input_consumed,
207            } => TranscodeDomainError::Main {
208                source: f(source),
209                input_index,
210                input_consumed,
211            },
212            Self::Finish { source } => TranscodeDomainError::Finish { source: f(source) },
213        }
214    }
215}