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}