qubit_codec/transcode/transcode_encode_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 encoders.
9
10use thiserror::Error;
11
12use super::capacity_error::CapacityError;
13use super::transcode_domain_error::TranscodeDomainError;
14use super::transcode_failure::TranscodeFailure;
15use crate::Codec;
16
17/// Encode transcode error for a codec-backed encoder.
18///
19/// # Type Parameters
20///
21/// - `C`: Codec supplying the domain error and input-value types.
22pub type TranscodeEncodeErrorOf<C> = TranscodeEncodeError<<C as Codec>::EncodeError, <C as Codec>::Value>;
23
24/// Error reported by an encode-oriented transcode operation.
25///
26/// # Type Parameters
27///
28/// - `E`: Domain error preserved from the codec or policy.
29/// - `V`: Optional owned context for an unencodable input value.
30///
31/// # Examples
32///
33/// ```
34/// use qubit_codec::TranscodeEncodeError;
35///
36/// let error = TranscodeEncodeError::<(), u8>::unencodable(3, 255);
37/// assert_eq!(error.unencodable_ref(), Some((3, Some(&255))));
38/// ```
39#[derive(Clone, Debug, Eq, Error, Hash, PartialEq)]
40#[must_use]
41pub enum TranscodeEncodeError<E, V> {
42 /// Framework-level transcode failure.
43 #[error(transparent)]
44 Failure(
45 /// Framework contract failure preserved as the error source.
46 #[from]
47 TranscodeFailure,
48 ),
49
50 /// The input value cannot be encoded by the target codec and policy.
51 #[error("unencodable value at input index {input_index}")]
52 Unencodable {
53 /// Absolute input index of the value being encoded.
54 input_index: usize,
55 /// `Some` owns the rejected value; `None` means value context was
56 /// unavailable.
57 value: Option<V>,
58 },
59
60 /// Domain-specific codec, charset, or policy error.
61 #[error(transparent)]
62 Domain(
63 /// Owned domain source and its reset, main, or finish context.
64 #[from]
65 TranscodeDomainError<E>,
66 ),
67}
68
69impl<E, V> TranscodeEncodeError<E, V> {
70 /// Creates a reset-phase domain-specific transcode error.
71 ///
72 /// # Parameters
73 ///
74 /// - `source`: Owned domain error from reset.
75 ///
76 /// # Returns
77 ///
78 /// Returns a domain error carrying reset-phase context.
79 #[inline]
80 pub const fn domain_reset(source: E) -> Self {
81 Self::Domain(TranscodeDomainError::reset(source))
82 }
83
84 /// Creates a main-phase domain-specific transcode error.
85 ///
86 /// # Parameters
87 ///
88 /// - `source`: Owned domain error during encoding.
89 /// - `input_index`: Absolute index of the failed input value.
90 ///
91 /// # Returns
92 ///
93 /// Returns a domain error carrying the input index and main-phase context.
94 #[inline]
95 pub const fn domain_main(source: E, input_index: usize) -> Self {
96 Self::Domain(TranscodeDomainError::main(source, input_index))
97 }
98
99 /// Creates a finish-phase domain-specific transcode error.
100 ///
101 /// # Parameters
102 ///
103 /// - `source`: Owned domain error from finalization.
104 ///
105 /// # Returns
106 ///
107 /// Returns a domain error carrying finish-phase context.
108 #[inline]
109 pub const fn domain_finish(source: E) -> Self {
110 Self::Domain(TranscodeDomainError::finish(source))
111 }
112
113 /// Creates an unencodable-value error with value context.
114 ///
115 /// # Parameters
116 ///
117 /// - `input_index`: Absolute index of the rejected value.
118 /// - `value`: Rejected value transferred into the error.
119 ///
120 /// # Returns
121 ///
122 /// Returns an unencodable error with owned value context.
123 #[inline]
124 pub fn unencodable(input_index: usize, value: V) -> Self {
125 Self::Unencodable {
126 input_index,
127 value: Some(value),
128 }
129 }
130
131 /// Creates an unencodable-value error without value context.
132 ///
133 /// # Parameters
134 ///
135 /// - `input_index`: Absolute index of the rejected input.
136 ///
137 /// # Returns
138 ///
139 /// Returns an unencodable error with no retained value.
140 #[inline]
141 pub const fn unencodable_without_context(input_index: usize) -> Self {
142 Self::Unencodable {
143 input_index,
144 value: None,
145 }
146 }
147
148 /// Returns whether this error wraps a domain error.
149 ///
150 /// # Returns
151 ///
152 /// Returns `true` only for the Domain variant.
153 #[inline]
154 #[must_use]
155 pub const fn is_domain(&self) -> bool {
156 matches!(self, Self::Domain(_))
157 }
158
159 /// Returns the framework failure carried by this error.
160 ///
161 /// # Returns
162 ///
163 /// Returns `Some` borrowed framework failure for Failure, otherwise `None`.
164 #[inline]
165 pub const fn failure_ref(&self) -> Option<&TranscodeFailure> {
166 match self {
167 Self::Failure(failure) => Some(failure),
168 Self::Unencodable { .. } | Self::Domain(_) => None,
169 }
170 }
171
172 /// Borrows the wrapped domain error and transcode context.
173 ///
174 /// # Returns
175 ///
176 /// Returns `Some` borrowed domain error and phase context for Domain,
177 /// or `None` for framework and unencodable errors.
178 #[inline]
179 pub const fn domain_error_ref(&self) -> Option<&TranscodeDomainError<E>> {
180 match self {
181 Self::Domain(error) => Some(error),
182 Self::Failure(_) | Self::Unencodable { .. } => None,
183 }
184 }
185
186 /// Borrows the wrapped domain error.
187 ///
188 /// # Returns
189 ///
190 /// Returns `Some` borrowed domain source for Domain, otherwise `None`.
191 #[inline]
192 pub const fn domain_ref(&self) -> Option<&E> {
193 match self {
194 Self::Domain(error) => Some(error.source()),
195 Self::Failure(_) | Self::Unencodable { .. } => None,
196 }
197 }
198
199 /// Borrows the unencodable value context carried by this error.
200 ///
201 /// # Returns
202 ///
203 /// Returns `Some((index, value))` for Unencodable, otherwise `None`.
204 /// The nested option is `Some` when a retained value can be borrowed,
205 /// and `None` when the error has only an input index.
206 #[inline]
207 pub const fn unencodable_ref(&self) -> Option<(usize, Option<&V>)> {
208 match self {
209 Self::Unencodable { input_index, value } => Some((*input_index, value.as_ref())),
210 Self::Failure(_) | Self::Domain(_) => None,
211 }
212 }
213
214 /// Maps the wrapped domain error while preserving other errors.
215 ///
216 /// # Type Parameters
217 ///
218 /// - `F`: Callback consuming the domain source.
219 /// - `T`: Replacement domain error type.
220 ///
221 /// # Parameters
222 ///
223 /// - `f`: Called exactly once for Domain, never for other variants.
224 ///
225 /// # Returns
226 ///
227 /// Returns an error with the transformed domain source while preserving
228 /// phase, input position, framework failures, and unencodable value
229 /// context.
230 #[inline]
231 pub fn map_domain<F, T>(self, f: F) -> TranscodeEncodeError<T, V>
232 where
233 F: FnOnce(E) -> T,
234 {
235 match self {
236 Self::Failure(failure) => TranscodeEncodeError::Failure(failure),
237 Self::Unencodable { input_index, value } => TranscodeEncodeError::Unencodable { input_index, value },
238 Self::Domain(error) => TranscodeEncodeError::Domain(error.map_source(f)),
239 }
240 }
241
242 /// Maps value context carried by unencodable-value errors.
243 ///
244 /// # Type Parameters
245 ///
246 /// - `F`: Callback consuming a retained value.
247 /// - `W`: Replacement value-context type.
248 ///
249 /// # Parameters
250 ///
251 /// - `f`: Called once for a retained unencodable value, otherwise not
252 /// called.
253 ///
254 /// # Returns
255 ///
256 /// Returns an error with transformed value context; absent context and
257 /// other variants are preserved.
258 #[inline]
259 pub fn map_value<F, W>(self, f: F) -> TranscodeEncodeError<E, W>
260 where
261 F: FnOnce(V) -> W,
262 {
263 match self {
264 Self::Failure(failure) => TranscodeEncodeError::Failure(failure),
265 Self::Unencodable { input_index, value } => TranscodeEncodeError::Unencodable {
266 input_index,
267 value: value.map(f),
268 },
269 Self::Domain(error) => TranscodeEncodeError::Domain(error),
270 }
271 }
272}
273
274impl<E, V> From<CapacityError> for TranscodeEncodeError<E, V> {
275 /// Converts capacity planning errors into transcode framework errors.
276 ///
277 /// # Parameters
278 ///
279 /// - `error`: Capacity failure from output planning.
280 ///
281 /// # Returns
282 ///
283 /// Returns the corresponding framework failure variant.
284 #[inline]
285 fn from(error: CapacityError) -> Self {
286 TranscodeFailure::from(error).into()
287 }
288}