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}