Skip to main content

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}