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::super::engine::TranscodeConvertEngine;
13use super::CodecTranscodeDecodeHooks;
14use super::CodecTranscodeEncodeHooks;
15use crate::CapacityError;
16use crate::Codec;
17use crate::TranscodeConvertErrorOf;
18use crate::TranscodeConverter;
19use crate::TranscodeProgress;
20use crate::Transcoder;
21
22/// Converts source units to target units through a decoded value by using
23/// codecs.
24///
25/// The converter decodes one source value with the decoder codec, then encodes
26/// that value with the encoder codec. If the current output buffer cannot hold
27/// the encoded value, the already decoded value is retained by the common
28/// converter engine and must be drained before more source input is consumed.
29/// Incomplete source tails are left in the caller-provided input slice while
30/// the stream is open; callers own input-buffer refill. After upstream EOF,
31/// call [`Transcoder::transcode_eof`] to let the source codec apply
32/// [`Codec::decode_eof`] and the configured incomplete-input policy to the
33/// remaining tail, then call [`Transcoder::finish`] to finalize the stream.
34/// This supports EOF-aware maximal-munch decoding when the codec expresses it
35/// through `decode_eof`. A custom transcoder or facade is needed only when the
36/// source framing requires behavior outside that codec EOF contract.
37///
38/// # Type Parameters
39///
40/// - `D`: Low-level codec used to decode source units.
41/// - `E`: Low-level codec used to encode target units.
42///
43/// # Examples
44///
45/// A byte-preserving codec can be adapted to the buffered streaming interface.
46///
47/// ```
48/// use core::convert::Infallible;
49/// use core::num::NonZeroUsize;
50///
51/// use qubit_codec::Codec;
52/// use qubit_codec::CodecTranscodeConverter;
53/// use qubit_codec::DecodeFailure;
54///
55/// # struct ByteCodec;
56/// # impl Codec for ByteCodec {
57/// #     type Value = u8;
58/// #     type Unit = u8;
59/// #     type DecodeError = Infallible;
60/// #     type EncodeError = Infallible;
61/// #     const MIN_UNITS_PER_VALUE: usize = 1;
62/// #     const MAX_ENCODE_UNITS_PER_VALUE: usize = 1;
63/// #     const MAX_DECODE_UNITS_PER_VALUE: usize = 1;
64/// #     unsafe fn decode(&mut self, input: &[u8], index: usize)
65/// #         -> Result<(u8, NonZeroUsize), DecodeFailure<Infallible>> {
66/// #         Ok((input[index], NonZeroUsize::MIN))
67/// #     }
68/// #     unsafe fn encode(&mut self, value: &u8, output: &mut [u8], index: usize)
69/// #         -> Result<usize, Infallible> {
70/// #         output[index] = *value;
71/// #         Ok(1)
72/// #     }
73/// # }
74/// let mut converter = CodecTranscodeConverter::new(ByteCodec, ByteCodec);
75/// let mut output = [0_u8; 2];
76/// converter.reset(&mut output, 0).unwrap();
77/// converter.transcode(&[7, 9], 0, &mut output, 0).unwrap();
78/// assert_eq!(output, [7, 9]);
79/// ```
80#[must_use]
81pub struct CodecTranscodeConverter<D, E>
82where
83    D: Codec,
84    E: Codec<Value = D::Value>,
85{
86    /// Common buffered converter engine.
87    engine: TranscodeConvertEngine<D, E, CodecTranscodeDecodeHooks, CodecTranscodeEncodeHooks>,
88}
89
90impl<D, E> CodecTranscodeConverter<D, E>
91where
92    D: Codec,
93    E: Codec<Value = D::Value>,
94{
95    /// Creates a buffered converter backed by decoder and encoder codecs.
96    ///
97    /// # Parameters
98    ///
99    /// - `decoder`: Low-level codec used to decode source units.
100    /// - `encoder`: Low-level codec used to encode target units.
101    ///
102    /// # Returns
103    ///
104    /// Returns a buffered converter adapter for the supplied codecs.
105    #[inline]
106    pub fn new(decoder: D, encoder: E) -> Self {
107        Self {
108            engine: TranscodeConvertEngine::new(decoder, encoder, CodecTranscodeDecodeHooks, CodecTranscodeEncodeHooks),
109        }
110    }
111
112    /// Returns a shared reference to the source codec.
113    ///
114    /// # Returns
115    ///
116    /// Borrows the source codec without allocating or changing stream state.
117    #[inline]
118    #[must_use]
119    pub fn source_codec(&self) -> &D {
120        self.engine.source_codec()
121    }
122
123    /// Returns a mutable reference to the source codec.
124    ///
125    /// Mutating a codec during an active stream can invalidate that stream's
126    /// assumptions; reset the adapter before continuing with the new codec
127    /// configuration.
128    ///
129    /// # Returns
130    ///
131    /// Exclusively borrows the codec while the adapter remains borrowed.
132    #[inline]
133    #[must_use]
134    pub fn source_codec_mut(&mut self) -> &mut D {
135        self.engine.source_codec_mut()
136    }
137
138    /// Returns a shared reference to the target codec.
139    ///
140    /// # Returns
141    ///
142    /// Borrows the target codec without allocating or changing stream state.
143    #[inline]
144    #[must_use]
145    pub fn target_codec(&self) -> &E {
146        self.engine.target_codec()
147    }
148
149    /// Returns a mutable reference to the target codec.
150    ///
151    /// Mutating a codec during an active stream can invalidate that stream's
152    /// assumptions; reset the adapter before continuing with the new codec
153    /// configuration.
154    ///
155    /// # Returns
156    ///
157    /// Exclusively borrows the codec while the adapter remains borrowed.
158    #[inline]
159    #[must_use]
160    pub fn target_codec_mut(&mut self) -> &mut E {
161        self.engine.target_codec_mut()
162    }
163
164    /// Consumes the adapter and returns its source and target codecs.
165    ///
166    /// Any buffered lifecycle state, pending value, and internal hooks are
167    /// discarded.
168    ///
169    /// # Returns
170    ///
171    /// Returns the owned source codec followed by the owned target codec.
172    #[inline]
173    #[must_use]
174    pub fn into_codecs(self) -> (D, E) {
175        let (source, target, _, _) = self.engine.into_parts();
176        (source, target)
177    }
178
179    /// Returns an upper bound for target units produced from `input_len` units.
180    ///
181    /// This concrete adapter method is available even when `D::Value` does not
182    /// implement [`Default`].
183    ///
184    /// # Parameters
185    ///
186    /// - `input_len`: Source units the caller plans to convert.
187    ///
188    /// # Returns
189    ///
190    /// Returns a conservative upper bound for produced target units.
191    ///
192    /// # Errors
193    ///
194    /// Returns [`CapacityError::OutputLengthOverflow`] when component-bound
195    /// arithmetic overflows.
196    #[inline]
197    pub fn max_transcode_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
198        self.engine.max_transcode_output_len(input_len)
199    }
200
201    /// Returns the global maximum target units emitted by finishing internal
202    /// state.
203    ///
204    /// # Returns
205    ///
206    /// Returns a conservative upper bound valid for every reachable converter
207    /// state.
208    ///
209    /// # Errors
210    ///
211    /// Returns [`CapacityError::OutputLengthOverflow`] when component-bound
212    /// arithmetic overflows.
213    #[inline]
214    pub fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
215        self.engine.max_finish_output_len()
216    }
217
218    /// Returns the maximum target units emitted when resetting stream state.
219    ///
220    /// # Returns
221    ///
222    /// Returns a conservative upper bound valid for every reachable converter
223    /// state.
224    ///
225    /// # Errors
226    ///
227    /// Returns [`CapacityError::OutputLengthOverflow`] when component-bound
228    /// arithmetic overflows.
229    #[inline]
230    pub fn max_reset_output_len(&self) -> Result<usize, CapacityError> {
231        self.engine.max_reset_output_len()
232    }
233
234    /// Clears retained pending output, resets target state, and then encodes
235    /// source-side reset values.
236    ///
237    /// `D::Value: Default` is required so the engine can allocate scratch
238    /// storage for any stream-start values the source decoder emits through
239    /// [`Codec::decode_reset`] before they are
240    /// piped through the target encoder. Stateless decoders never reach the
241    /// allocating path; the bound is consulted only when
242    /// [`Codec::MAX_DECODE_RESET_VALUES`]
243    /// is non-zero.
244    ///
245    /// # Parameters
246    ///
247    /// - `output`: Target unit slice for stream-start output.
248    /// - `output_index`: Absolute target output index where writing starts.
249    ///
250    /// # Returns
251    ///
252    /// Returns the number of target units written while resetting component
253    /// state.
254    ///
255    /// # Errors
256    ///
257    /// Returns a converter error when the output range is invalid or too
258    /// small, or when decoder or encoder reset processing fails.
259    pub fn reset(&mut self, output: &mut [E::Unit], output_index: usize) -> Result<usize, TranscodeConvertErrorOf<D, E>>
260    where
261        D::Value: Default,
262    {
263        self.engine.reset(output, output_index)
264    }
265
266    /// Converts source units into target units.
267    ///
268    /// This method's signature does not require `D::Value: Default`. Starting
269    /// and finishing a usable stream through the current reset/finish APIs
270    /// still requires that bound. Removing the lifecycle scratch bound is
271    /// deferred.
272    ///
273    /// # Parameters
274    ///
275    /// - `input`: Source unit slice.
276    /// - `input_index`: Absolute source index where conversion starts.
277    /// - `output`: Target unit slice.
278    /// - `output_index`: Absolute target index where writing starts.
279    ///
280    /// # Returns
281    ///
282    /// Returns conversion progress for consumed/produced counters and stop
283    /// reason.
284    ///
285    /// # Errors
286    ///
287    /// Returns converter error when source or target indices are invalid, or
288    /// when decoding/encoding fails under current policy.
289    pub fn transcode(
290        &mut self,
291        input: &[D::Unit],
292        input_index: usize,
293        output: &mut [E::Unit],
294        output_index: usize,
295    ) -> Result<TranscodeProgress, TranscodeConvertErrorOf<D, E>> {
296        self.engine.transcode(input, input_index, output, output_index)
297    }
298
299    /// Converts source units after the caller has established end of input.
300    ///
301    /// # Parameters
302    ///
303    /// - `input`: Final source unit slice; the caller must not supply more
304    ///   input later.
305    /// - `input_index`: Absolute index at which unread source units begin.
306    /// - `output`: Target unit slice receiving converted values.
307    /// - `output_index`: Absolute index at which target output begins.
308    ///
309    /// # Returns
310    ///
311    /// Returns consumed and produced counts with the conversion stop reason.
312    ///
313    /// # Errors
314    ///
315    /// Returns a converter error for invalid indices, invalid or incomplete
316    /// final source input, or a decoder/encoder failure under the current
317    /// policy.
318    pub fn transcode_eof(
319        &mut self,
320        input: &[D::Unit],
321        input_index: usize,
322        output: &mut [E::Unit],
323        output_index: usize,
324    ) -> Result<TranscodeProgress, TranscodeConvertErrorOf<D, E>> {
325        self.engine.transcode_eof(input, input_index, output, output_index)
326    }
327
328    /// Finishes internally retained output after EOF.
329    ///
330    /// Finalization delegates to the reusable converter engine. It drains
331    /// retained pending output, encodes source-side decode flush values, and
332    /// then finishes target-side encode hook state.
333    ///
334    /// # Parameters
335    ///
336    /// - `output`: Target unit slice for finalization output.
337    /// - `output_index`: Absolute target output index where writing starts.
338    ///
339    /// # Returns
340    ///
341    /// Returns the number of target units written by finalization.
342    ///
343    /// # Errors
344    ///
345    /// Returns a converter error for invalid output indices, insufficient
346    /// output capacity, or failures while draining pending values or
347    /// finishing either codec.
348    pub fn finish(
349        &mut self,
350        output: &mut [E::Unit],
351        output_index: usize,
352    ) -> Result<usize, TranscodeConvertErrorOf<D, E>>
353    where
354        D::Value: Default,
355    {
356        self.engine.finish(output, output_index)
357    }
358}
359
360impl<D, E> Transcoder for CodecTranscodeConverter<D, E>
361where
362    D: Codec,
363    E: Codec<Value = D::Value>,
364    D::Value: Default,
365{
366    /// Source units accepted by the decoder codec.
367    type Input = D::Unit;
368    /// Target units produced by the encoder codec.
369    type Output = E::Unit;
370    /// Conversion failures retaining decoder, encoder, and pending-value
371    /// context.
372    type Error = TranscodeConvertErrorOf<D, E>;
373
374    /// Returns an upper bound for target units produced from `input_len` units.
375    ///
376    /// # Parameters
377    ///
378    /// - `input_len`: Source units the caller plans to convert.
379    ///
380    /// # Returns
381    ///
382    /// Returns a conservative upper bound for produced target units.
383    ///
384    /// # Errors
385    ///
386    /// Returns [`CapacityError::OutputLengthOverflow`] when component-bound
387    /// arithmetic overflows.
388    #[inline]
389    fn max_transcode_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
390        CodecTranscodeConverter::max_transcode_output_len(self, input_len)
391    }
392
393    /// Returns the maximum target units emitted by finishing internal state.
394    ///
395    /// # Returns
396    ///
397    /// Returns a conservative upper bound valid for every reachable converter
398    /// state.
399    ///
400    /// # Errors
401    ///
402    /// Returns [`CapacityError::OutputLengthOverflow`] when component-bound
403    /// arithmetic overflows.
404    #[inline]
405    fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
406        CodecTranscodeConverter::max_finish_output_len(self)
407    }
408
409    /// Returns the maximum target units emitted when resetting stream state.
410    ///
411    /// # Returns
412    ///
413    /// Returns a conservative upper bound valid for every reachable converter
414    /// state.
415    ///
416    /// # Errors
417    ///
418    /// Returns [`CapacityError::OutputLengthOverflow`] when component-bound
419    /// arithmetic overflows.
420    #[inline]
421    fn max_reset_output_len(&self) -> Result<usize, CapacityError> {
422        CodecTranscodeConverter::max_reset_output_len(self)
423    }
424
425    /// Clears retained pending output, resets component state, and emits
426    /// stream-start encode output.
427    ///
428    /// # Parameters
429    ///
430    /// - `output`: Target unit slice for stream-start output.
431    /// - `output_index`: Absolute target output index where writing starts.
432    ///
433    /// # Returns
434    ///
435    /// Returns the number of target units written while resetting component
436    /// state.
437    ///
438    /// # Errors
439    ///
440    /// Returns a converter error when the output range is invalid or too
441    /// small, or when decoder or encoder reset processing fails.
442    fn reset(&mut self, output: &mut [E::Unit], output_index: usize) -> Result<usize, TranscodeConvertErrorOf<D, E>> {
443        CodecTranscodeConverter::reset(self, output, output_index)
444    }
445
446    /// Converts source units into target units.
447    ///
448    /// # Parameters
449    ///
450    /// - `input`: Source unit slice.
451    /// - `input_index`: Absolute source index where conversion starts.
452    /// - `output`: Target unit slice.
453    /// - `output_index`: Absolute target index where writing starts.
454    ///
455    /// # Returns
456    ///
457    /// Returns conversion progress for consumed/produced counters and stop
458    /// reason.
459    ///
460    /// # Errors
461    ///
462    /// Returns converter error when source or target indices are invalid, or
463    /// when decoding/encoding fails under current policy.
464    fn transcode(
465        &mut self,
466        input: &[D::Unit],
467        input_index: usize,
468        output: &mut [E::Unit],
469        output_index: usize,
470    ) -> Result<TranscodeProgress, TranscodeConvertErrorOf<D, E>> {
471        CodecTranscodeConverter::transcode(self, input, input_index, output, output_index)
472    }
473
474    /// Transcodes an input segment while marking it as the end of the stream.
475    ///
476    /// This forwarding implementation delegates to the converter's EOF-aware
477    /// operation so buffered decoder state can be finalized before encoding.
478    ///
479    /// # Parameters
480    ///
481    /// - `input`: Final source unit slice; the caller must not supply more
482    ///   input later.
483    /// - `input_index`: Absolute index at which unread source units begin.
484    /// - `output`: Target unit slice receiving converted values.
485    /// - `output_index`: Absolute index at which target output begins.
486    ///
487    /// # Returns
488    ///
489    /// Returns consumed and produced counts with the conversion stop reason.
490    ///
491    /// # Errors
492    ///
493    /// Returns a converter error for invalid indices, invalid or incomplete
494    /// final source input, or a decoder/encoder failure under the current
495    /// policy.
496    fn transcode_eof(
497        &mut self,
498        input: &[D::Unit],
499        input_index: usize,
500        output: &mut [E::Unit],
501        output_index: usize,
502    ) -> Result<TranscodeProgress, TranscodeConvertErrorOf<D, E>> {
503        CodecTranscodeConverter::transcode_eof(self, input, input_index, output, output_index)
504    }
505
506    /// Finishes internally retained output after EOF.
507    ///
508    /// # Parameters
509    ///
510    /// - `output`: Target unit slice for finalization output.
511    /// - `output_index`: Absolute target output index where writing starts.
512    ///
513    /// # Returns
514    ///
515    /// Returns the number of target units written by finalization.
516    ///
517    /// # Errors
518    ///
519    /// Returns a converter error for invalid output indices, insufficient
520    /// output capacity, or failures while draining pending values or
521    /// finishing either codec.
522    fn finish(&mut self, output: &mut [E::Unit], output_index: usize) -> Result<usize, TranscodeConvertErrorOf<D, E>> {
523        CodecTranscodeConverter::finish(self, output, output_index)
524    }
525}
526
527impl<D, E> TranscodeConverter for CodecTranscodeConverter<D, E>
528where
529    D: Codec,
530    E: Codec<Value = D::Value>,
531    D::Value: Default,
532{
533    /// Domain error reported by the source codec.
534    type DecodeError = D::DecodeError;
535    /// Domain error reported by the target codec.
536    type EncodeError = E::EncodeError;
537    /// Decoded value transferred from source codec to target codec.
538    type Value = D::Value;
539}
540
541impl<D, E> Default for CodecTranscodeConverter<D, E>
542where
543    D: Codec,
544    E: Codec<Value = D::Value>,
545    TranscodeConvertEngine<D, E, CodecTranscodeDecodeHooks, CodecTranscodeEncodeHooks>: Default,
546{
547    /// Creates a default codec-backed buffered converter.
548    ///
549    /// # Returns
550    ///
551    /// Returns a converter with default codecs and hooks.
552    #[inline]
553    fn default() -> Self {
554        Self {
555            engine: TranscodeConvertEngine::default(),
556        }
557    }
558}
559
560impl<D, E> fmt::Debug for CodecTranscodeConverter<D, E>
561where
562    D: Codec,
563    E: Codec<Value = D::Value>,
564    TranscodeConvertEngine<D, E, CodecTranscodeDecodeHooks, CodecTranscodeEncodeHooks>: fmt::Debug,
565{
566    /// Formats the wrapped converter engine for debugging.
567    ///
568    /// # Parameters
569    ///
570    /// - `f`: Destination formatter.
571    ///
572    /// # Returns
573    ///
574    /// Returns `fmt::Result` from the formatter.
575    ///
576    /// # Errors
577    ///
578    /// Returns the formatter error if writing the debug representation fails.
579    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
580        f.debug_struct("CodecTranscodeConverter")
581            .field("engine", &self.engine)
582            .finish()
583    }
584}