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}