Skip to main content

qubit_codec/buffered/
codec_buffered_converter.rs

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