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(
104        source: E,
105        input_index: usize,
106        input_consumed: Option<NonZeroUsize>,
107    ) -> Self {
108        Self::Main {
109            source,
110            input_index,
111            input_consumed,
112        }
113    }
114
115    /// Creates a finish-phase domain error.
116    ///
117    /// # Parameters
118    ///
119    /// - `source`: Domain error returned by finish handling.
120    ///
121    /// # Returns
122    ///
123    /// Returns a finish-phase domain error.
124    #[inline(always)]
125    #[must_use]
126    pub const fn finish(source: E) -> Self {
127        Self::Finish { source }
128    }
129
130    /// Returns the wrapped source error.
131    ///
132    /// # Returns
133    ///
134    /// Returns a shared reference to the domain error.
135    #[inline(always)]
136    #[must_use]
137    pub const fn source(&self) -> &E {
138        match self {
139            Self::Reset { source }
140            | Self::Main { source, .. }
141            | Self::Finish { source } => source,
142        }
143    }
144
145    /// Consumes this error and returns the wrapped source error.
146    ///
147    /// # Returns
148    ///
149    /// Returns the domain error.
150    #[inline(always)]
151    #[must_use]
152    pub fn into_source(self) -> E {
153        match self {
154            Self::Reset { source }
155            | Self::Main { source, .. }
156            | Self::Finish { source } => source,
157        }
158    }
159
160    /// Returns the absolute input index associated with the error.
161    ///
162    /// # Returns
163    ///
164    /// Returns `Some(index)` for main-phase errors and `None` for reset or
165    /// finish errors.
166    #[inline(always)]
167    #[must_use]
168    pub const fn input_index(&self) -> Option<usize> {
169        match self {
170            Self::Main { input_index, .. } => Some(*input_index),
171            Self::Reset { .. } | Self::Finish { .. } => None,
172        }
173    }
174
175    /// Returns invalid input consumption associated with the error.
176    ///
177    /// # Returns
178    ///
179    /// Returns `Some(consumed)` when a main-phase decode error reports the
180    /// invalid input width. Returns `None` otherwise.
181    #[inline(always)]
182    #[must_use]
183    pub const fn input_consumed(&self) -> Option<NonZeroUsize> {
184        match self {
185            Self::Main { input_consumed, .. } => *input_consumed,
186            Self::Reset { .. } | Self::Finish { .. } => None,
187        }
188    }
189
190    /// Maps the wrapped source error while preserving phase context.
191    ///
192    /// # Type Parameters
193    ///
194    /// - `T`: Target source error type.
195    /// - `F`: Mapping function type.
196    ///
197    /// # Parameters
198    ///
199    /// - `f`: Function applied to the wrapped source error.
200    ///
201    /// # Returns
202    ///
203    /// Returns the mapped domain error.
204    #[inline]
205    pub fn map_source<T, F>(self, f: F) -> TranscodeDomainError<T>
206    where
207        F: FnOnce(E) -> T,
208    {
209        match self {
210            Self::Reset { source } => {
211                TranscodeDomainError::Reset { source: f(source) }
212            }
213            Self::Main {
214                source,
215                input_index,
216                input_consumed,
217            } => TranscodeDomainError::Main {
218                source: f(source),
219                input_index,
220                input_consumed,
221            },
222            Self::Finish { source } => {
223                TranscodeDomainError::Finish { source: f(source) }
224            }
225        }
226    }
227}