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}