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    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}