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