Skip to main content

qubit_codec/transcode/adapter/
codec_transcode_converter.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//! Buffered converter adapter backed by two low-level codecs.
9
10use core::fmt;
11
12use super::{
13    CodecTranscodeDecodeHooks,
14    CodecTranscodeEncodeHooks,
15};
16use crate::{
17    CapacityError,
18    Codec,
19    CodecConvertError,
20    CodecDecodeError,
21    CodecEncodeError,
22    TranscodeConvertEngine,
23    TranscodeConvertEngineError,
24    TranscodeConverter,
25    TranscodeDecodeEngineError,
26    TranscodeEncodeEngineError,
27    TranscodeError,
28    TranscodeProgress,
29    Transcoder,
30};
31
32/// Strict codec-backed converter error type.
33type CodecTranscodeConvertError<D, E> =
34    CodecConvertError<<D as Codec>::DecodeError, <E as Codec>::EncodeError>;
35
36type CodecTranscodeConvertEngineError<D, E> = TranscodeConvertEngineError<
37    TranscodeDecodeEngineError<
38        <D as Codec>::DecodeError,
39        CodecDecodeError<<D as Codec>::DecodeError>,
40    >,
41    TranscodeEncodeEngineError<
42        <E as Codec>::EncodeError,
43        CodecEncodeError<<E as Codec>::EncodeError>,
44    >,
45>;
46
47/// Converts source units to target units through a decoded value by using
48/// codecs.
49///
50/// The converter decodes one source value with the decoder codec, then encodes
51/// that value with the encoder codec. If the current output buffer cannot hold
52/// the encoded value, the already decoded value is retained by the common
53/// converter engine and must be drained before more source input is consumed.
54/// Incomplete source tails are left in the caller-provided input slice; callers
55/// own input-buffer refill and EOF incomplete-tail policy.
56///
57/// Because finalization receives no source input, the source codec should have
58/// locally decidable decode boundaries for the default converter bridge. Source
59/// formats that require EOF-aware maximal-munch parsing or delayed boundary
60/// decisions should implement that source-side policy in a custom transcoder or
61/// facade before conversion.
62///
63/// # Type Parameters
64///
65/// - `D`: Low-level codec used to decode source units.
66/// - `E`: Low-level codec used to encode target units.
67pub struct CodecTranscodeConverter<D, E>
68where
69    D: Codec,
70    E: Codec<Value = D::Value>,
71{
72    /// Common buffered converter engine.
73    engine: TranscodeConvertEngine<
74        D,
75        E,
76        CodecTranscodeDecodeHooks,
77        CodecTranscodeEncodeHooks,
78    >,
79}
80
81impl<D, E> fmt::Debug for CodecTranscodeConverter<D, E>
82where
83    D: Codec,
84    E: Codec<Value = D::Value>,
85    TranscodeConvertEngine<
86        D,
87        E,
88        CodecTranscodeDecodeHooks,
89        CodecTranscodeEncodeHooks,
90    >: fmt::Debug,
91{
92    /// Formats the wrapped converter engine for debugging.
93    ///
94    /// # Parameters
95    ///
96    /// - `f`: Destination formatter.
97    ///
98    /// # Returns
99    ///
100    /// Returns `fmt::Result` from the formatter.
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        f.debug_struct("CodecTranscodeConverter")
103            .field("engine", &self.engine)
104            .finish()
105    }
106}
107
108impl<D, E> CodecTranscodeConverter<D, E>
109where
110    D: Codec,
111    E: Codec<Value = D::Value>,
112{
113    /// Creates a buffered converter backed by decoder and encoder codecs.
114    ///
115    /// # Parameters
116    ///
117    /// - `decoder`: Low-level codec used to decode source units.
118    /// - `encoder`: Low-level codec used to encode target units.
119    ///
120    /// # Returns
121    ///
122    /// Returns a buffered converter adapter for the supplied codecs.
123    #[inline(always)]
124    #[must_use]
125    pub fn new(decoder: D, encoder: E) -> Self {
126        Self {
127            engine: TranscodeConvertEngine::new(
128                decoder,
129                encoder,
130                CodecTranscodeDecodeHooks,
131                CodecTranscodeEncodeHooks,
132            ),
133        }
134    }
135
136    /// Returns an upper bound for target units produced from `input_len` units.
137    ///
138    /// This concrete adapter method is available even when `D::Value` does not
139    /// implement [`Default`].
140    ///
141    /// # Parameters
142    ///
143    /// - `input_len`: Source units the caller plans to convert.
144    ///
145    /// # Returns
146    ///
147    /// Returns a conservative upper bound for produced target units.
148    #[must_use = "capacity planning can fail on overflow"]
149    #[inline(always)]
150    pub fn max_output_len(
151        &self,
152        input_len: usize,
153    ) -> Result<usize, CapacityError> {
154        self.engine.max_output_len(input_len)
155    }
156
157    /// Returns the maximum target units emitted by finishing internal state.
158    ///
159    /// # Returns
160    ///
161    /// Returns a conservative upper bound for remaining converter-final output.
162    #[must_use = "capacity planning can fail on overflow"]
163    #[inline(always)]
164    pub fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
165        self.engine.max_finish_output_len()
166    }
167
168    /// Returns the maximum target units emitted when resetting stream state.
169    #[must_use = "capacity planning can fail on overflow"]
170    #[inline(always)]
171    pub fn max_reset_output_len(&self) -> Result<usize, CapacityError> {
172        self.engine.max_reset_output_len()
173    }
174
175    /// Clears retained pending output and hook state and emits stream-start
176    /// encode output.
177    ///
178    /// `D::Value: Default` is required so the engine can allocate scratch
179    /// storage for any stream-start values the source decoder emits through
180    /// [`Codec::decode_reset`](crate::Codec::decode_reset) before they are
181    /// piped through the target encoder. Stateless decoders never reach the
182    /// allocating path; the bound is consulted only when
183    /// [`Codec::MAX_DECODE_RESET_VALUES`](crate::Codec::MAX_DECODE_RESET_VALUES)
184    /// is non-zero.
185    #[inline(always)]
186    pub fn reset(
187        &mut self,
188        output: &mut [E::Unit],
189        output_index: usize,
190    ) -> Result<usize, TranscodeError<CodecTranscodeConvertError<D, E>>>
191    where
192        D::Value: Default,
193    {
194        self.engine.reset(output, output_index).map_err(|error| {
195            error.map_domain(flatten_convert_engine_error::<D, E>)
196        })
197    }
198
199    /// Converts source units into target units.
200    ///
201    /// This is the main streaming operation and does not require `D::Value` to
202    /// implement [`Default`].
203    ///
204    /// # Parameters
205    ///
206    /// - `input`: Source unit slice.
207    /// - `input_index`: Absolute source index where conversion starts.
208    /// - `output`: Target unit slice.
209    /// - `output_index`: Absolute target index where writing starts.
210    ///
211    /// # Returns
212    ///
213    /// Returns conversion progress for consumed/produced counters and stop
214    /// reason.
215    ///
216    /// # Errors
217    ///
218    /// Returns converter error when source or target indices are invalid, or
219    /// when decoding/encoding fails under current policy.
220    #[inline(always)]
221    pub fn transcode(
222        &mut self,
223        input: &[D::Unit],
224        input_index: usize,
225        output: &mut [E::Unit],
226        output_index: usize,
227    ) -> Result<
228        TranscodeProgress,
229        TranscodeError<CodecTranscodeConvertError<D, E>>,
230    > {
231        self.engine
232            .transcode(input, input_index, output, output_index)
233            .map_err(|error| {
234                error.map_domain(flatten_convert_engine_error::<D, E>)
235            })
236    }
237
238    /// Finishes internally retained output after EOF.
239    ///
240    /// Finalization delegates to the reusable converter engine. It drains
241    /// retained pending output, encodes source-side decode flush values, and
242    /// then finishes target-side encode hook state.
243    ///
244    /// # Parameters
245    ///
246    /// - `output`: Target unit slice for finalization output.
247    /// - `output_index`: Absolute target output index where writing starts.
248    ///
249    /// # Returns
250    ///
251    /// Returns the number of target units written by finalization.
252    ///
253    /// # Errors
254    ///
255    /// Returns a finish error for pending output that cannot be finalized.
256    #[inline(always)]
257    pub fn finish(
258        &mut self,
259        output: &mut [E::Unit],
260        output_index: usize,
261    ) -> Result<usize, TranscodeError<CodecTranscodeConvertError<D, E>>>
262    where
263        D::Value: Default,
264    {
265        self.engine.finish(output, output_index).map_err(|error| {
266            error.map_domain(flatten_convert_engine_error::<D, E>)
267        })
268    }
269}
270
271impl<D, E> Transcoder<D::Unit, E::Unit> for CodecTranscodeConverter<D, E>
272where
273    D: Codec,
274    E: Codec<Value = D::Value>,
275    D::Value: Default,
276{
277    type Error = CodecConvertError<D::DecodeError, E::EncodeError>;
278
279    /// Returns an upper bound for target units produced from `input_len` units.
280    ///
281    /// # Parameters
282    ///
283    /// - `input_len`: Source units the caller plans to convert.
284    ///
285    /// # Returns
286    ///
287    /// Returns a conservative upper bound for produced target units.
288    #[inline(always)]
289    fn max_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
290        CodecTranscodeConverter::max_output_len(self, input_len)
291    }
292
293    /// Returns the maximum target units emitted by finishing internal state.
294    ///
295    /// # Returns
296    ///
297    /// Returns a conservative upper bound for remaining converter-final output.
298    #[inline(always)]
299    fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
300        CodecTranscodeConverter::max_finish_output_len(self)
301    }
302
303    /// Returns the maximum target units emitted when resetting stream state.
304    #[inline(always)]
305    fn max_reset_output_len(&self) -> Result<usize, CapacityError> {
306        CodecTranscodeConverter::max_reset_output_len(self)
307    }
308
309    /// Clears retained pending output, resets component state, and emits
310    /// stream-start encode output.
311    #[inline(always)]
312    fn reset(
313        &mut self,
314        output: &mut [E::Unit],
315        output_index: usize,
316    ) -> Result<usize, TranscodeError<Self::Error>> {
317        CodecTranscodeConverter::reset(self, output, output_index)
318    }
319
320    /// Converts source units into target units.
321    ///
322    /// # Parameters
323    ///
324    /// - `input`: Source unit slice.
325    /// - `input_index`: Absolute source index where conversion starts.
326    /// - `output`: Target unit slice.
327    /// - `output_index`: Absolute target index where writing starts.
328    ///
329    /// # Returns
330    ///
331    /// Returns conversion progress for consumed/produced counters and stop
332    /// reason.
333    ///
334    /// # Errors
335    ///
336    /// Returns converter error when source or target indices are invalid, or
337    /// when decoding/encoding fails under current policy.
338    #[inline(always)]
339    fn transcode(
340        &mut self,
341        input: &[D::Unit],
342        input_index: usize,
343        output: &mut [E::Unit],
344        output_index: usize,
345    ) -> Result<TranscodeProgress, TranscodeError<Self::Error>> {
346        CodecTranscodeConverter::transcode(
347            self,
348            input,
349            input_index,
350            output,
351            output_index,
352        )
353    }
354
355    /// Finishes internally retained output after EOF.
356    ///
357    /// # Parameters
358    ///
359    /// - `output`: Target unit slice for finalization output.
360    /// - `output_index`: Absolute target output index where writing starts.
361    ///
362    /// # Returns
363    ///
364    /// Returns the number of target units written by finalization.
365    ///
366    /// # Errors
367    ///
368    /// Returns a finish error for pending output that cannot be finalized.
369    #[inline(always)]
370    fn finish(
371        &mut self,
372        output: &mut [E::Unit],
373        output_index: usize,
374    ) -> Result<usize, TranscodeError<Self::Error>> {
375        CodecTranscodeConverter::finish(self, output, output_index)
376    }
377}
378
379impl<D, E> TranscodeConverter<D::Unit, E::Unit>
380    for CodecTranscodeConverter<D, E>
381where
382    D: Codec,
383    E: Codec<Value = D::Value>,
384    D::Value: Default,
385{
386    // empty
387}
388
389impl<D, E> Default for CodecTranscodeConverter<D, E>
390where
391    D: Codec,
392    E: Codec<Value = D::Value>,
393    TranscodeConvertEngine<
394        D,
395        E,
396        CodecTranscodeDecodeHooks,
397        CodecTranscodeEncodeHooks,
398    >: Default,
399{
400    /// Creates a default codec-backed buffered converter.
401    ///
402    /// # Returns
403    ///
404    /// Returns a converter with default codecs and hooks.
405    #[inline(always)]
406    fn default() -> Self {
407        Self {
408            engine: TranscodeConvertEngine::default(),
409        }
410    }
411}
412
413#[inline(always)]
414fn flatten_convert_engine_error<D, E>(
415    error: CodecTranscodeConvertEngineError<D, E>,
416) -> CodecTranscodeConvertError<D, E>
417where
418    D: Codec,
419    E: Codec<Value = D::Value>,
420{
421    match error {
422        TranscodeConvertEngineError::Decode(error) => match error {
423            TranscodeDecodeEngineError::Codec(error)
424            | TranscodeDecodeEngineError::Hook(error) => {
425                CodecConvertError::decode(error)
426            }
427        },
428        TranscodeConvertEngineError::Encode(error) => match error {
429            TranscodeEncodeEngineError::Codec(error)
430            | TranscodeEncodeEngineError::Hook(error) => {
431                CodecConvertError::encode(error)
432            }
433        },
434    }
435}