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 #[must_use]
166 pub const fn failure_ref(&self) -> Option<&TranscodeFailure> {
167 match self {
168 Self::Failure(failure) => Some(failure),
169 Self::Unencodable { .. } | Self::Domain(_) => None,
170 }
171 }
172
173 /// Borrows the wrapped domain error and transcode context.
174 ///
175 /// # Returns
176 ///
177 /// Returns `Some` borrowed domain error and phase context for Domain,
178 /// or `None` for framework and unencodable errors.
179 #[inline]
180 #[must_use]
181 pub const fn domain_error_ref(&self) -> Option<&TranscodeDomainError<E>> {
182 match self {
183 Self::Domain(error) => Some(error),
184 Self::Failure(_) | Self::Unencodable { .. } => None,
185 }
186 }
187
188 /// Borrows the wrapped domain error.
189 ///
190 /// # Returns
191 ///
192 /// Returns `Some` borrowed domain source for Domain, otherwise `None`.
193 #[inline]
194 #[must_use]
195 pub const fn domain_ref(&self) -> Option<&E> {
196 match self {
197 Self::Domain(error) => Some(error.source()),
198 Self::Failure(_) | Self::Unencodable { .. } => None,
199 }
200 }
201
202 /// Borrows the unencodable value context carried by this error.
203 ///
204 /// # Returns
205 ///
206 /// Returns `Some((index, value))` for Unencodable, otherwise `None`.
207 /// The nested option is `Some` when a retained value can be borrowed,
208 /// and `None` when the error has only an input index.
209 #[inline]
210 #[must_use]
211 pub const fn unencodable_ref(&self) -> Option<(usize, Option<&V>)> {
212 match self {
213 Self::Unencodable { input_index, value } => Some((*input_index, value.as_ref())),
214 Self::Failure(_) | Self::Domain(_) => None,
215 }
216 }
217
218 /// Maps the wrapped domain error while preserving other errors.
219 ///
220 /// # Type Parameters
221 ///
222 /// - `F`: Callback consuming the domain source.
223 /// - `T`: Replacement domain error type.
224 ///
225 /// # Parameters
226 ///
227 /// - `f`: Called exactly once for Domain, never for other variants.
228 ///
229 /// # Returns
230 ///
231 /// Returns an error with the transformed domain source while preserving
232 /// phase, input position, framework failures, and unencodable value
233 /// context.
234 #[inline]
235 pub fn map_domain<F, T>(self, f: F) -> TranscodeEncodeError<T, V>
236 where
237 F: FnOnce(E) -> T,
238 {
239 match self {
240 Self::Failure(failure) => TranscodeEncodeError::Failure(failure),
241 Self::Unencodable { input_index, value } => TranscodeEncodeError::Unencodable { input_index, value },
242 Self::Domain(error) => TranscodeEncodeError::Domain(error.map_source(f)),
243 }
244 }
245
246 /// Maps value context carried by unencodable-value errors.
247 ///
248 /// # Type Parameters
249 ///
250 /// - `F`: Callback consuming a retained value.
251 /// - `W`: Replacement value-context type.
252 ///
253 /// # Parameters
254 ///
255 /// - `f`: Called once for a retained unencodable value, otherwise not
256 /// called.
257 ///
258 /// # Returns
259 ///
260 /// Returns an error with transformed value context; absent context and
261 /// other variants are preserved.
262 #[inline]
263 pub fn map_value<F, W>(self, f: F) -> TranscodeEncodeError<E, W>
264 where
265 F: FnOnce(V) -> W,
266 {
267 match self {
268 Self::Failure(failure) => TranscodeEncodeError::Failure(failure),
269 Self::Unencodable { input_index, value } => TranscodeEncodeError::Unencodable {
270 input_index,
271 value: value.map(f),
272 },
273 Self::Domain(error) => TranscodeEncodeError::Domain(error),
274 }
275 }
276}
277
278impl<E, V> From<CapacityError> for TranscodeEncodeError<E, V> {
279 /// Converts capacity planning errors into transcode framework errors.
280 ///
281 /// # Parameters
282 ///
283 /// - `error`: Capacity failure from output planning.
284 ///
285 /// # Returns
286 ///
287 /// Returns the corresponding framework failure variant.
288 #[inline]
289 fn from(error: CapacityError) -> Self {
290 TranscodeFailure::from(error).into()
291 }
292}