Skip to main content

qubit_codec/transcode/engine/
transcode_convert_engine.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//! Reusable buffered converter engine for codec-backed transcoding.
9//!
10//! Bridges a source [`crate::engine::TranscodeDecodeEngine`] and a target
11//! [`crate::engine::TranscodeEncodeEngine`] into one unit-to-unit conversion
12//! pipeline.
13
14use core::num::NonZeroUsize;
15
16use qubit_utils::try_reserve_vec;
17
18use super::super::internal::convert_state::ConvertState;
19use super::super::internal::encode_attempt::EncodeAttempt;
20use super::super::internal::lifecycle_guard::LifecycleGuard;
21use super::super::internal::pending_value::PendingValue;
22use super::super::internal::pending_value_slot::PendingValueSlot;
23use super::TranscodeDecodeHooks;
24use super::TranscodeEncodeHooks;
25use super::transcode_decode_engine::TranscodeDecodeEngine;
26use super::transcode_encode_engine::TranscodeEncodeEngine;
27use crate::CapacityError;
28use crate::Codec;
29use crate::TranscodeConvertError;
30use crate::TranscodeConvertErrorOf;
31use crate::TranscodeConverter;
32use crate::TranscodeFailure;
33use crate::TranscodeProgress;
34use crate::Transcoder;
35use crate::codec::assert_unit_bounds;
36
37/// Adds two independent target-output capacity bounds.
38///
39/// # Parameters
40///
41/// - `first`: First independent output bound.
42/// - `second`: Second independent output bound.
43///
44/// # Returns
45///
46/// The checked sum of the supplied target-unit bounds.
47///
48/// # Errors
49///
50/// Returns [`CapacityError::OutputLengthOverflow`] when the sum exceeds
51/// `usize`.
52#[inline]
53fn add_convert_output_bounds(first: usize, second: usize) -> Result<usize, CapacityError> {
54    first.checked_add(second).ok_or(CapacityError::OutputLengthOverflow)
55}
56
57/// Adds three independent target-output capacity bounds.
58///
59/// # Parameters
60///
61/// - `first`: First independent target-output bound.
62/// - `second`: Second independent target-output bound.
63/// - `third`: Third independent target-output bound.
64///
65/// # Returns
66///
67/// The checked sum of the supplied target-unit bounds.
68///
69/// # Errors
70///
71/// Returns [`CapacityError::OutputLengthOverflow`] when the sum exceeds
72/// `usize`.
73#[inline]
74fn sum_convert_output_bounds(first: usize, second: usize, third: usize) -> Result<usize, CapacityError> {
75    let partial = add_convert_output_bounds(first, second)?;
76    add_convert_output_bounds(partial, third)
77}
78
79/// Asserts that a pre-reserved conversion phase did not need more output.
80///
81/// # Parameters
82///
83/// - `progress`: `None` after draining, or `Some` when output is still needed.
84/// - `message`: Invariant explanation included in a panic.
85///
86/// # Panics
87///
88/// Panics with `message` if `progress` is `Some`.
89#[inline]
90fn assert_reserved_output_drained(progress: Option<TranscodeProgress>, message: &'static str) {
91    assert!(progress.is_none(), "{message}");
92}
93
94/// Reusable buffered conversion engine for codec-backed converters.
95///
96/// The engine owns reusable buffered decode and encode engines. It keeps
97/// common converter control flow private: index validation, pending-value
98/// retention, finish draining, decode-error policy dispatch, encode attempts,
99/// output-capacity checks, and [`crate::TranscodeStatus`] reporting.
100///
101/// Use this type to build a streaming converter over two one-value [`Codec`]
102/// implementations that share the same logical value type. Each hot-path step
103/// decodes one source unit sequence into a value, then immediately tries to
104/// encode that value into the target output buffer. If the target buffer lacks
105/// capacity, the decoded value is retained in an internal pending slot and
106/// must be drained before more source input is consumed, preserving output
107/// order across buffer turns.
108///
109/// `TranscodeConvertEngine` is intentionally batch-oriented. Its public
110/// [`Self::transcode`] method drives a source/output buffer loop and reuses the
111/// same unchecked codec and hook primitives as
112/// [`crate::engine::TranscodeDecodeEngine`] and
113/// [`crate::engine::TranscodeEncodeEngine`]. It does not call one-value public
114/// transcoders in the hot path.
115///
116/// Values emitted by the source codec's decode reset or finish phase have no
117/// source-unit position. The converter reports the placeholder index 0 for
118/// their encode contexts and errors. This index cannot distinguish
119/// lifecycle-generated values from a value decoded at source index 0. Error
120/// phases describe the underlying codec operation: encoding such a value uses
121/// the main encode path, even during converter reset or finish. Ordinary
122/// decoded values retain their index in the input slice supplied to the
123/// decode call that produced them. A pending value keeps that index across
124/// calls; it is not rebased to a later input slice and is not a whole-stream
125/// offset. The retained index may be outside the bounds of a later input
126/// slice.
127///
128/// For strict codec-backed conversion with default decode and encode policies,
129/// use [`crate::CodecTranscodeConverter`]. Use `TranscodeConvertEngine`
130/// directly when either side needs custom malformed-input repair, encode
131/// planning, skipped values, or finish-time output.
132///
133/// The engine follows the same lifecycle as [`crate::Transcoder`]:
134/// `reset → transcode* → finish → reset`. Call [`Self::reset`] before starting
135/// a new logical stream and [`Self::finish`] after EOF once any incomplete
136/// source tail has been handled.
137///
138/// # Examples
139///
140/// ```rust
141/// use core::convert::Infallible;
142/// use core::num::NonZeroUsize;
143///
144/// use qubit_codec::Codec;
145/// use qubit_codec::DecodeFailure;
146/// use qubit_codec::TranscodeDecodeErrorOf;
147/// use qubit_codec::TranscodeEncodeErrorOf;
148/// use qubit_codec::TranscodeStatus;
149/// use qubit_codec::engine::DecodeContext;
150/// use qubit_codec::engine::EncodeContext;
151/// use qubit_codec::engine::EncodeUnencodableAction;
152/// use qubit_codec::engine::TranscodeConvertEngine;
153/// use qubit_codec::engine::TranscodeDecodeHooks;
154/// use qubit_codec::engine::TranscodeEncodeHooks;
155///
156/// #[derive(Clone, Copy)]
157/// struct SourceCodec;
158///
159/// #[derive(Clone, Copy)]
160/// struct TargetCodec;
161///
162/// impl Codec for SourceCodec {
163///     type Value = u8;
164///     type Unit = u8;
165///     type DecodeError = Infallible;
166///     type EncodeError = Infallible;
167///
168///     const MIN_UNITS_PER_VALUE: usize = 1;
169///     const MAX_ENCODE_UNITS_PER_VALUE: usize = 1;
170///     const MAX_DECODE_UNITS_PER_VALUE: usize = 1;
171///
172///     unsafe fn decode(
173///         &mut self,
174///         input: &[u8],
175///         index: usize,
176///     ) -> Result<(u8, NonZeroUsize), DecodeFailure<Self::DecodeError>> {
177///         Ok((input[index].wrapping_add(1), NonZeroUsize::MIN))
178///     }
179///
180///     unsafe fn encode(
181///         &mut self,
182///         value: &u8,
183///         output: &mut [u8],
184///         index: usize,
185///     ) -> Result<usize, Self::EncodeError> {
186///         output[index] = *value;
187///         Ok(1)
188///     }
189/// }
190///
191/// impl Codec for TargetCodec {
192///     type Value = u8;
193///     type Unit = u8;
194///     type DecodeError = Infallible;
195///     type EncodeError = Infallible;
196///
197///     const MIN_UNITS_PER_VALUE: usize = 1;
198///     const MAX_ENCODE_UNITS_PER_VALUE: usize = 1;
199///     const MAX_DECODE_UNITS_PER_VALUE: usize = 1;
200///
201///     unsafe fn decode(
202///         &mut self,
203///         input: &[u8],
204///         index: usize,
205///     ) -> Result<(u8, NonZeroUsize), DecodeFailure<Self::DecodeError>> {
206///         Ok((input[index], NonZeroUsize::MIN))
207///     }
208///
209///     unsafe fn encode(
210///         &mut self,
211///         value: &u8,
212///         output: &mut [u8],
213///         index: usize,
214///     ) -> Result<usize, Self::EncodeError> {
215///         output[index] = *value;
216///         Ok(1)
217///     }
218/// }
219///
220/// struct StrictDecodeHooks;
221///
222/// impl TranscodeDecodeHooks<SourceCodec> for StrictDecodeHooks {
223///     fn handle_invalid_decode(
224///         &mut self,
225///         _codec: &mut SourceCodec,
226///         error: &Infallible,
227///         _consumed: Option<NonZeroUsize>,
228///         _context: DecodeContext,
229///     ) -> Result<qubit_codec::engine::DecodeInvalidAction<u8>, TranscodeDecodeErrorOf<SourceCodec>> {
230///         match *error {}
231///     }
232/// }
233///
234/// struct StrictEncodeHooks;
235///
236/// impl TranscodeEncodeHooks<TargetCodec> for StrictEncodeHooks {
237///     fn handle_unencodable_encode(
238///         &mut self,
239///         _codec: &mut TargetCodec,
240///         _context: &EncodeContext<'_, u8>,
241///     ) -> Result<EncodeUnencodableAction<u8>, TranscodeEncodeErrorOf<TargetCodec>> {
242///         unreachable!("TargetCodec accepts every u8")
243///     }
244/// }
245///
246/// let mut engine = TranscodeConvertEngine::new(
247///     SourceCodec,
248///     TargetCodec,
249///     StrictDecodeHooks,
250///     StrictEncodeHooks,
251/// );
252/// let input = [1_u8, 2, 3];
253/// let mut output = [0_u8; 2];
254/// let mut reset_output = [];
255/// engine.reset(&mut reset_output, 0)?;
256///
257/// let progress = engine.transcode(&input, 0, &mut output, 0)?;
258/// match progress.status() {
259///     TranscodeStatus::NeedOutput { .. } => {
260///         assert_eq!(2, progress.written());
261///         assert_eq!([2, 3], output);
262///         // Drain `output[..progress.written()]`, then resume at
263///         // `progress.read()` with fresh output capacity.
264///     }
265///     TranscodeStatus::Complete => unreachable!("output is intentionally short"),
266///     TranscodeStatus::NeedInput { .. } => unreachable!("input is complete"),
267/// }
268/// # Ok::<(), qubit_codec::TranscodeConvertErrorOf<SourceCodec, TargetCodec>>(())
269/// ```
270///
271/// # Type Parameters
272///
273/// - `D`: Source-side decoder codec.
274/// - `E`: Target-side encoder codec.
275/// - `DH`: Source-side decode hooks.
276/// - `EH`: Target-side encode hooks.
277#[derive(Debug)]
278pub struct TranscodeConvertEngine<D, E, DH, EH>
279where
280    D: Codec,
281    E: Codec<Value = D::Value>,
282    DH: TranscodeDecodeHooks<D>,
283    EH: TranscodeEncodeHooks<E>,
284{
285    /// Source-side buffered decoder engine.
286    decode_engine: TranscodeDecodeEngine<D, DH>,
287    /// Target-side buffered encoder engine.
288    encode_engine: TranscodeEncodeEngine<E, EH>,
289    /// Decoded value waiting for target output capacity.
290    pending: PendingValueSlot<D::Value>,
291    /// Guard for the `reset → transcode* → finish` lifecycle in every build
292    /// profile. The converter owns its own guard rather than delegating to the
293    /// inner decode/encode engines, because lifecycle events here describe the
294    /// converter as a whole.
295    lifecycle: LifecycleGuard,
296}
297
298impl<D, E, DH, EH> TranscodeConvertEngine<D, E, DH, EH>
299where
300    D: Codec,
301    E: Codec<Value = D::Value>,
302    DH: TranscodeDecodeHooks<D>,
303    EH: TranscodeEncodeHooks<E>,
304{
305    /// Creates a buffered converter engine.
306    ///
307    /// The caller supplies decode hooks and encode hooks directly.
308    ///
309    /// # Parameters
310    ///
311    /// - `decoder`: Low-level codec used for source decoding.
312    /// - `encoder`: Low-level codec used for target encoding.
313    /// - `decode_hooks`: Decode-side policy hooks.
314    /// - `encode_hooks`: Encode-side policy hooks.
315    ///
316    /// # Returns
317    ///
318    /// Returns a buffered converter engine.
319    ///
320    /// # Compile-Time Checks
321    ///
322    /// Fails to compile when either codec declares a zero decode unit bound or
323    /// when [`Codec::MIN_UNITS_PER_VALUE`] exceeds
324    /// [`Codec::MAX_DECODE_UNITS_PER_VALUE`].
325    #[inline]
326    #[must_use]
327    pub fn new(decoder: D, encoder: E, decode_hooks: DH, encode_hooks: EH) -> Self {
328        assert_unit_bounds::<D>();
329        assert_unit_bounds::<E>();
330        Self {
331            decode_engine: TranscodeDecodeEngine::new(decoder, decode_hooks),
332            encode_engine: TranscodeEncodeEngine::new(encoder, encode_hooks),
333            pending: PendingValueSlot::empty(),
334            lifecycle: LifecycleGuard::new(),
335        }
336    }
337
338    /// Returns the source-side codec.
339    ///
340    /// # Returns
341    ///
342    /// Returns a shared reference to the decoder codec owned by this engine.
343    #[inline]
344    #[must_use]
345    pub fn source_codec(&self) -> &D {
346        self.decode_engine.codec()
347    }
348
349    /// Returns the source-side codec mutably.
350    ///
351    /// # Returns
352    ///
353    /// Returns a mutable reference to the decoder codec owned by this engine.
354    #[inline]
355    #[must_use]
356    pub fn source_codec_mut(&mut self) -> &mut D {
357        self.decode_engine.codec_mut()
358    }
359
360    /// Returns the target-side codec.
361    ///
362    /// # Returns
363    ///
364    /// Returns a shared reference to the encoder codec owned by this engine.
365    #[inline]
366    #[must_use]
367    pub fn target_codec(&self) -> &E {
368        self.encode_engine.codec()
369    }
370
371    /// Returns the target-side codec mutably.
372    ///
373    /// # Returns
374    ///
375    /// Returns a mutable reference to the encoder codec owned by this engine.
376    #[inline]
377    #[must_use]
378    pub fn target_codec_mut(&mut self) -> &mut E {
379        self.encode_engine.codec_mut()
380    }
381
382    /// Returns the decode hooks used by this engine.
383    ///
384    /// # Returns
385    ///
386    /// Returns a shared reference to the decode hooks owned by this engine.
387    #[inline]
388    #[must_use]
389    pub const fn decode_hooks(&self) -> &DH {
390        self.decode_engine.hooks()
391    }
392
393    /// Returns the decode hooks mutably.
394    ///
395    /// Mutating the returned hooks does not reset the converter or clear a
396    /// pending value. The replacement hooks must continue to satisfy their
397    /// global output-capacity contract for every reachable converter state.
398    ///
399    /// # Returns
400    ///
401    /// Returns a mutable reference to the decode hooks owned by this engine.
402    #[inline]
403    #[must_use]
404    pub fn decode_hooks_mut(&mut self) -> &mut DH {
405        self.decode_engine.hooks_mut()
406    }
407
408    /// Returns the encode hooks used by this engine.
409    ///
410    /// # Returns
411    ///
412    /// Returns a shared reference to the encode hooks owned by this engine.
413    #[inline]
414    #[must_use]
415    pub const fn encode_hooks(&self) -> &EH {
416        self.encode_engine.hooks()
417    }
418
419    /// Returns the encode hooks mutably.
420    ///
421    /// Mutating the returned hooks does not reset the converter or clear a
422    /// pending value. The replacement hooks must continue to satisfy their
423    /// global output-capacity contract for every reachable converter state.
424    ///
425    /// # Returns
426    ///
427    /// Returns a mutable reference to the encode hooks owned by this engine.
428    #[inline]
429    #[must_use]
430    pub fn encode_hooks_mut(&mut self) -> &mut EH {
431        self.encode_engine.hooks_mut()
432    }
433
434    /// Consumes the engine and returns its codecs and hooks.
435    ///
436    /// Any pending value and lifecycle state owned by the converter are
437    /// discarded. Callers should use this only when no further conversion state
438    /// needs to be preserved.
439    ///
440    /// # Returns
441    ///
442    /// Returns the source codec, target codec, decode hooks, and encode hooks.
443    #[inline]
444    #[must_use]
445    pub fn into_parts(self) -> (D, E, DH, EH) {
446        let Self {
447            decode_engine,
448            encode_engine,
449            ..
450        } = self;
451        let (source, decode_hooks) = decode_engine.into_parts();
452        let (target, encode_hooks) = encode_engine.into_parts();
453        (source, target, decode_hooks, encode_hooks)
454    }
455
456    /// Returns an upper bound for target units produced from `input_len` units.
457    ///
458    /// The bound sums three parts: one possible retained pending value, the
459    /// maximum decoded values from the source side, and the maximum target
460    /// units for those values on the encode side. It covers only the streaming
461    /// convert phase and remains valid even when no value is currently pending.
462    /// Downstream converters must use this engine-level API for capacity
463    /// planning instead of recomputing the bound from the source or target
464    /// [`Codec`] constants.
465    ///
466    /// # Parameters
467    ///
468    /// - `input_len`: Number of source units the caller plans to convert.
469    ///
470    /// # Returns
471    ///
472    /// Returns a conservative upper bound, or a capacity error on arithmetic
473    /// overflow.
474    ///
475    /// # Errors
476    ///
477    /// Returns [`CapacityError`] when a component bound or their sum overflows
478    /// `usize`.
479    pub fn max_transcode_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
480        let pending_units = self.encode_engine.max_transcode_output_len(1)?;
481        let decoded_values = self.decode_engine.max_transcode_output_len(input_len)?;
482        let converted_units = self.encode_engine.max_transcode_output_len(decoded_values)?;
483        add_convert_output_bounds(converted_units, pending_units)
484    }
485
486    /// Returns the global maximum target units emitted when resetting stream
487    /// state.
488    ///
489    /// Covers decode-side reset values (encoded to target units) plus
490    /// encode-side reset units. Most codecs are stateless and return `0`
491    /// for [`Codec::MAX_DECODE_RESET_VALUES`]; in that case this equals the
492    /// encode reset bound only.
493    ///
494    /// # Returns
495    ///
496    /// Returns the combined decode-reset and encode-reset output bound, or a
497    /// capacity error on arithmetic overflow.
498    ///
499    /// # Errors
500    ///
501    /// Returns [`CapacityError`] when a component bound or their sum overflows
502    /// `usize`.
503    pub fn max_reset_output_len(&self) -> Result<usize, CapacityError> {
504        let decode_reset_units = self
505            .encode_engine
506            .max_transcode_output_len(D::MAX_DECODE_RESET_VALUES)?;
507        let encode_reset_units = E::MAX_ENCODE_RESET_UNITS;
508        add_convert_output_bounds(decode_reset_units, encode_reset_units)
509    }
510
511    /// Returns the maximum target units emitted by finishing retained state.
512    ///
513    /// The bound covers one possible retained pending value, the global
514    /// decode-side finish-value bound (encoded to target units), and the
515    /// global encode-side finish-unit bound. It is independent of whether a
516    /// pending value currently exists.
517    ///
518    /// # Returns
519    ///
520    /// Returns the combined pending, decode-finish, and encode-finish output
521    /// bound, or a capacity error on arithmetic overflow.
522    ///
523    /// # Errors
524    ///
525    /// Returns [`CapacityError`] when a component bound or their sum overflows
526    /// `usize`.
527    pub fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
528        let pending_units = self.encode_engine.max_transcode_output_len(1)?;
529        let decoder_finish_values = self.decode_engine.max_finish_output_len()?;
530        let decoder_finish_units = self.encode_engine.max_transcode_output_len(decoder_finish_values)?;
531        let encoder_finish_units = self.encode_engine.max_finish_output_len()?;
532        sum_convert_output_bounds(pending_units, decoder_finish_units, encoder_finish_units)
533    }
534
535    /// Returns the maximum target units needed by a complete one-shot
536    /// conversion.
537    ///
538    /// The returned bound covers conversion reset output, the streaming convert
539    /// phase for `input_len` source units, and finish output. Its components
540    /// are global across transient state. Higher-level complete conversion
541    /// helpers should use this engine-level bound instead of recomputing
542    /// capacity from the source or target codec constants, because decode
543    /// and encode hooks may change streaming or finish output.
544    ///
545    /// # Parameters
546    ///
547    /// - `input_len`: Number of source units in the complete stream.
548    ///
549    /// # Returns
550    ///
551    /// Returns the complete-stream target-output bound, or a capacity error on
552    /// arithmetic overflow.
553    ///
554    /// # Errors
555    ///
556    /// Returns [`CapacityError`] when a component bound or their sum overflows
557    /// `usize`.
558    pub fn max_total_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
559        let reset = self.max_reset_output_len()?;
560        let transcode = self.max_transcode_output_len(input_len)?;
561        let finish = self.max_finish_output_len()?;
562        sum_convert_output_bounds(reset, transcode, finish)
563    }
564
565    /// Clears retained conversion state, runs before-reset hooks, and emits
566    /// stream-start encode output.
567    ///
568    /// Reset clears any retained pending value, resets the target encoder, then
569    /// drains decode-side reset values through that reset encoder state. The
570    /// caller must provide enough output capacity for
571    /// [`Self::max_reset_output_len`].
572    ///
573    /// # Parameters
574    ///
575    /// - `output`: Complete output unit slice visible to the converter.
576    /// - `output_index`: Absolute output index where writing starts.
577    ///
578    /// # Returns
579    ///
580    /// Returns the number of target units written while resetting stream state.
581    ///
582    /// # Errors
583    ///
584    /// Returns a converter error if reset validation, target reset output
585    /// emission, or reservation of the internal lifecycle value buffer fails.
586    /// Capacity and index failures occur before converter state is changed.
587    /// Once lifecycle execution starts, failure leaves the converter poisoned
588    /// until a later reset succeeds.
589    ///
590    /// # Panics
591    ///
592    /// Panics when decode-reset values cannot be encoded within the capacity
593    /// reserved by [`Self::max_reset_output_len`].
594    pub fn reset(&mut self, output: &mut [E::Unit], output_index: usize) -> Result<usize, TranscodeConvertErrorOf<D, E>>
595    where
596        D::Value: Default,
597    {
598        let required = self.max_reset_output_len()?;
599        TranscodeFailure::ensure_output_capacity(output.len(), output_index, required)?;
600        self.lifecycle.on_reset_start();
601
602        self.pending.clear();
603
604        // Reset the target first because source-side reset values must be
605        // encoded under the target's new-stream state. The reset bound reserves
606        // space for both target-owned output and the encoded source-reset
607        // values, so encode_pending should never report `NeedOutput` here.
608        let empty_input: &[D::Unit] = &[];
609        let mut state = ConvertState::new(empty_input, 0, output, output_index);
610        let output_cursor = state.output_cursor();
611        let encoder_written = self.encode_engine.reset(state.output_mut(), output_cursor)?;
612        state.advance_output(encoder_written);
613        self.drain_decoder_reset(&mut state)?;
614        self.lifecycle.on_reset_success();
615        Ok(state.written())
616    }
617
618    /// Converts source units into target units.
619    ///
620    /// The engine drains any retained pending value before consuming new input.
621    /// Each loop iteration decodes one source value and immediately attempts to
622    /// encode it. Conversion stops when the input tail is incomplete, when the
623    /// output buffer cannot hold the next encoded value, or when the visible
624    /// input is exhausted.
625    ///
626    /// # Parameters
627    ///
628    /// - `input`: Complete input unit slice visible to the converter.
629    /// - `input_index`: Absolute input index where conversion starts.
630    /// - `output`: Complete output unit slice visible to the converter.
631    /// - `output_index`: Absolute output index where writing starts.
632    ///
633    /// # Returns
634    ///
635    /// Returns conversion progress describing input units consumed, target
636    /// units written, and why conversion stopped.
637    ///
638    /// # Errors
639    ///
640    /// Returns [`TranscodeConvertError::Failure`] with
641    /// [`TranscodeFailure::InvalidInputIndex`] for an invalid input index or
642    /// [`TranscodeFailure::InvalidOutputIndex`] for an invalid output index.
643    /// Concrete codec or hook conversion errors retain their decode-side or
644    /// encode-side classification. Returns
645    /// [`TranscodeFailure::TranscodeBeforeReset`] when the
646    /// engine has not completed its first reset. Returns
647    /// [`TranscodeFailure::TranscodeAfterFinish`] when the logical stream was
648    /// already finished and has not been reset, or
649    /// [`TranscodeFailure::LifecyclePoisoned`] when an earlier reset or finish
650    /// failed after execution started.
651    ///
652    /// # Panics
653    ///
654    /// Panics when a codec or hook violates its declared decode or encode
655    /// bounds, including a decode hook consuming beyond the available input,
656    /// or when an encode hook supplies an unencodable replacement value.
657    #[inline]
658    pub fn transcode(
659        &mut self,
660        input: &[D::Unit],
661        input_index: usize,
662        output: &mut [E::Unit],
663        output_index: usize,
664    ) -> Result<TranscodeProgress, TranscodeConvertErrorOf<D, E>> {
665        self.transcode_with_eof(input, input_index, output, output_index, false)
666    }
667
668    /// Converts source units after the caller has established end of input.
669    ///
670    /// # Parameters
671    ///
672    /// - `input`: Source units visible to this call.
673    /// - `input_index`: Starting index in `input`.
674    /// - `output`: Destination for target units.
675    /// - `output_index`: Starting index in `output`.
676    ///
677    /// # Returns
678    ///
679    /// Consumed source units, written target units, and the stopping reason.
680    ///
681    /// # Errors
682    ///
683    /// Returns an error for invalid indices, a missing reset, a finished or
684    /// poisoned stream, or codec/hook conversion failure.
685    ///
686    /// # Panics
687    ///
688    /// Panics when a codec or hook violates its declared decode or encode
689    /// bounds, including a decode hook consuming beyond the available input,
690    /// or when an encode hook supplies an unencodable replacement value.
691    #[inline]
692    pub fn transcode_eof(
693        &mut self,
694        input: &[D::Unit],
695        input_index: usize,
696        output: &mut [E::Unit],
697        output_index: usize,
698    ) -> Result<TranscodeProgress, TranscodeConvertErrorOf<D, E>> {
699        self.transcode_with_eof(input, input_index, output, output_index, true)
700    }
701
702    /// Finishes retained output after EOF.
703    ///
704    /// Finalization drains a pending decoded value first, then lets the
705    /// source-side decode hooks emit final values, encodes those values through
706    /// the target-side encode hooks, and finally finishes target-side encode
707    /// hook state. The decode-finish value buffer used for this cold path
708    /// requires `D::Value: Default`; the normal [`Self::transcode`] loop does
709    /// not.
710    ///
711    /// # Parameters
712    ///
713    /// - `output`: Complete output unit slice visible to the converter.
714    /// - `output_index`: Absolute output index where writing starts.
715    ///
716    /// # Returns
717    ///
718    /// Returns the number of target units written during finalization.
719    ///
720    /// # Errors
721    ///
722    /// Returns a converter error when output capacity checks fail, the
723    /// internal lifecycle value buffer cannot be reserved, or hook
724    /// finalization fails. Returns
725    /// [`TranscodeFailure::FinishBeforeReset`] when the engine has not
726    /// completed its first reset. Returns
727    /// [`TranscodeFailure::FinishAfterFinish`] when the logical stream was
728    /// already finished and has not been reset, or
729    /// [`TranscodeFailure::LifecyclePoisoned`] when an earlier reset or finish
730    /// failed after execution started. Capacity and index failures occur before
731    /// finish execution and remain retryable; later failures poison the
732    /// converter until reset succeeds. Once lifecycle execution starts,
733    /// failure leaves the converter poisoned until a later reset succeeds.
734    ///
735    /// # Panics
736    ///
737    /// Panics when a retained pending value or decode-finish value cannot be
738    /// encoded within the planned finish capacity.
739    pub fn finish(
740        &mut self,
741        output: &mut [E::Unit],
742        output_index: usize,
743    ) -> Result<usize, TranscodeConvertErrorOf<D, E>>
744    where
745        D::Value: Default,
746    {
747        self.lifecycle.on_finish_attempt()?;
748        let required = self.current_finish_output_len()?;
749        TranscodeFailure::ensure_output_capacity(output.len(), output_index, required)?;
750        self.lifecycle.on_finish_start();
751
752        let empty_input: &[D::Unit] = &[];
753        let mut state = ConvertState::new(empty_input, 0, output, output_index);
754        // Finish keeps the same priority as transcode: output any retained
755        // decoded value before asking source-side hooks for final values.
756        let progress = self.drain_pending(&mut state)?;
757        assert_reserved_output_drained(progress, "converter finish bound must reserve space for pending values");
758
759        // Source-side finish may emit one or more final values. Drain them into
760        // the target encoder before finishing target-side hook state.
761        self.drain_decoder_finish(&mut state)?;
762
763        let output_cursor = state.output_cursor();
764        let written = self.encode_engine.finish(state.output_mut(), output_cursor)?;
765        state.advance_output(written);
766        self.lifecycle.on_finish_success();
767        Ok(state.written())
768    }
769
770    /// Runs a complete one-shot `reset -> transcode -> finish` conversion.
771    ///
772    /// The complete input is supplied as `input`, and output starts at index
773    /// `0` in `output`. Callers that need subranges should slice their
774    /// buffers before calling this method. Downstream one-shot converter
775    /// helpers should call this engine method instead of reproducing the
776    /// reset, transcode, and finish sequence themselves.
777    ///
778    /// # Parameters
779    ///
780    /// - `input`: Complete source unit slice.
781    /// - `output`: Target unit slice for the whole converted stream.
782    ///
783    /// # Returns
784    ///
785    /// Returns the number of target units written.
786    ///
787    /// # Errors
788    ///
789    /// Returns framework errors for insufficient output, capacity overflow, or
790    /// an incomplete EOF tail, and domain errors from reset, conversion, or
791    /// finish.
792    ///
793    /// # Panics
794    ///
795    /// Panics if codec/hook lifecycle output exceeds its declared capacity
796    /// bound.
797    #[inline]
798    pub fn transcode_complete_into(
799        &mut self,
800        input: &[D::Unit],
801        output: &mut [E::Unit],
802    ) -> Result<usize, TranscodeConvertErrorOf<D, E>>
803    where
804        D::Value: Default,
805    {
806        <Self as Transcoder>::transcode_complete_into(self, input, output)
807    }
808
809    /// Returns the finish-output bound for the converter's current pending
810    /// state.
811    ///
812    /// Public capacity methods expose global bounds across every transient
813    /// state. A concrete finish call can use this narrower checked bound while
814    /// still relying on global component bounds for decoder and encoder
815    /// finalization.
816    ///
817    /// # Returns
818    ///
819    /// Returns the current finish-output bound, or a capacity error on
820    /// arithmetic overflow.
821    ///
822    /// # Errors
823    ///
824    /// Returns [`CapacityError`] when a component bound or their sum overflows
825    /// `usize`.
826    fn current_finish_output_len(&self) -> Result<usize, CapacityError> {
827        let pending_units = self.pending.current_output_len(&self.encode_engine)?;
828        let decoder_finish_values = self.decode_engine.max_finish_output_len()?;
829        let decoder_finish_units = self.encode_engine.max_transcode_output_len(decoder_finish_values)?;
830        let encoder_finish_units = self.encode_engine.max_finish_output_len()?;
831        sum_convert_output_bounds(pending_units, decoder_finish_units, encoder_finish_units)
832    }
833
834    /// Runs one conversion step with an explicit end-of-input flag.
835    ///
836    /// The helper owns the shared lifecycle and cursor checks used by both
837    /// streaming and EOF-aware public entry points.
838    ///
839    /// # Parameters
840    ///
841    /// - `input`: Source units visible to this call.
842    /// - `input_index`: Starting index in `input`.
843    /// - `output`: Destination for target units.
844    /// - `output_index`: Starting index in `output`.
845    /// - `end_of_input`: Whether the source is closed and EOF decode rules
846    ///   apply.
847    ///
848    /// # Returns
849    ///
850    /// Consumed source units, written target units, and the stopping reason.
851    ///
852    /// # Errors
853    ///
854    /// Returns an error for invalid indices, a missing reset, a finished or
855    /// poisoned stream, or codec/hook conversion failure.
856    fn transcode_with_eof(
857        &mut self,
858        input: &[D::Unit],
859        input_index: usize,
860        output: &mut [E::Unit],
861        output_index: usize,
862        end_of_input: bool,
863    ) -> Result<TranscodeProgress, TranscodeConvertErrorOf<D, E>> {
864        self.lifecycle.on_transcode()?;
865        TranscodeFailure::ensure_transcode_indices(input.len(), input_index, output.len(), output_index)?;
866
867        let mut state = ConvertState::new(input, input_index, output, output_index);
868
869        // A retained decoded value must be written before consuming more input,
870        // otherwise callers could observe output reordered across buffer turns.
871        if let Some(progress) = self.drain_pending(&mut state)? {
872            return Ok(progress);
873        }
874
875        let min_input_units =
876            NonZeroUsize::new(D::MIN_UNITS_PER_VALUE).expect("Codec::MIN_UNITS_PER_VALUE is non-zero");
877        let min_input_len = min_input_units.get();
878        while state.has_input() {
879            let available = state.available_input();
880            if available < min_input_len && !end_of_input {
881                return Ok(state.need_input_progress(min_input_units));
882            }
883
884            let previous_read = state.read();
885            // Each hot-path step decodes one source value and immediately tries
886            // to encode it, preserving backpressure at the target output.
887            if let Some(progress) = self.convert_next(&mut state, end_of_input)? {
888                return Ok(progress);
889            }
890            debug_assert!(
891                state.read() > previous_read,
892                "TranscodeConvertEngine conversion step must consume input or stop",
893            );
894        }
895
896        Ok(state.complete_progress())
897    }
898
899    /// Drains source-side decode reset output and encodes emitted reset
900    /// values.
901    ///
902    /// Stateless decoders still call [`TranscodeDecodeEngine::reset`] so hook
903    /// teardown side effects run even when no reset values are emitted.
904    ///
905    /// # Parameters
906    ///
907    /// - `state`: Current conversion cursors and output buffer.
908    ///
909    /// # Returns
910    ///
911    /// Returns `Ok(())` after all decode-reset values have been encoded.
912    ///
913    /// # Errors
914    ///
915    /// Returns a converter error when decode reset or encode reset handling
916    /// fails.
917    ///
918    /// # Panics
919    ///
920    /// Panics when a decode-reset value cannot be encoded within the capacity
921    /// reserved by [`Self::max_reset_output_len`].
922    fn drain_decoder_reset(
923        &mut self,
924        state: &mut ConvertState<'_, D::Unit, E::Unit>,
925    ) -> Result<(), TranscodeConvertErrorOf<D, E>>
926    where
927        D::Value: Default,
928    {
929        let value_count = D::MAX_DECODE_RESET_VALUES;
930        if value_count == 0 {
931            // Stateless decoder: still call decode_reset so codecs whose
932            // hooks own teardown side effects (e.g. clearing accumulators)
933            // run them. The empty slice is safe because the capacity check
934            // inside `reset` accepts `required == 0` against any slice.
935            self.decode_engine.reset(&mut [], 0)?;
936            return Ok(());
937        }
938        // `D::Value: Default` is only consulted when the decoder declares
939        // reset output. Stateless codecs never reach this branch.
940        let mut reset_values = Vec::new();
941        try_reserve_vec(&mut reset_values, value_count).map_err(|_| TranscodeFailure::allocation_failed())?;
942        reset_values.resize_with(value_count, D::Value::default);
943        let written = self.decode_engine.reset(&mut reset_values, 0)?;
944        for value in reset_values.into_iter().take(written) {
945            let pending = PendingValue::new(value, 0);
946            let progress = self.encode_pending(pending, state)?;
947            assert_reserved_output_drained(
948                progress,
949                "converter reset bound must reserve space for decode reset values",
950            );
951        }
952        Ok(())
953    }
954
955    /// Converts one source value from the current state cursors.
956    ///
957    /// Decodes one value through the source engine, then immediately attempts
958    /// to encode it through the target engine.
959    ///
960    /// # Parameters
961    ///
962    /// - `state`: Current conversion cursors and output buffer.
963    /// - `end_of_input`: Whether to apply EOF-aware decode rules.
964    ///
965    /// # Returns
966    ///
967    /// Returns conversion progress when the step stops early, or `None` when
968    /// the value was consumed and either encoded or skipped by policy.
969    ///
970    /// # Errors
971    ///
972    /// Returns a converter error when decode or encode handling fails.
973    #[inline]
974    fn convert_next(
975        &mut self,
976        state: &mut ConvertState<'_, D::Unit, E::Unit>,
977        end_of_input: bool,
978    ) -> Result<Option<TranscodeProgress>, TranscodeConvertErrorOf<D, E>> {
979        let (outcome, pending) =
980            self.decode_engine
981                .decode_one(state.input(), state.decode_context(), end_of_input, PendingValue::new)?;
982        if let Some(pending) = pending {
983            self.pending.put(pending);
984        }
985        if let Some(progress) = state.apply_decode_outcome(outcome) {
986            return Ok(Some(progress));
987        }
988        let Some(pending) = self.pending.take() else {
989            return Ok(None);
990        };
991        self.encode_pending(pending, state)
992    }
993
994    /// Writes a retained decoded value before new input is consumed.
995    ///
996    /// # Parameters
997    ///
998    /// - `state`: Current conversion cursors and output buffer.
999    ///
1000    /// # Returns
1001    ///
1002    /// Returns conversion progress when the pending value needs more output
1003    /// capacity, or `None` when no value was pending or the value was encoded
1004    /// or skipped by policy.
1005    ///
1006    /// # Errors
1007    ///
1008    /// Returns a converter error when encode handling fails.
1009    #[inline]
1010    fn drain_pending(
1011        &mut self,
1012        state: &mut ConvertState<'_, D::Unit, E::Unit>,
1013    ) -> Result<Option<TranscodeProgress>, TranscodeConvertErrorOf<D, E>> {
1014        let Some(pending) = self.pending.take() else {
1015            return Ok(None);
1016        };
1017        self.encode_pending(pending, state)
1018    }
1019
1020    /// Drains source-side decode finish output and encodes emitted final
1021    /// values.
1022    ///
1023    /// When the decoder declares no finish output, still calls
1024    /// [`TranscodeDecodeEngine::finish`] so codec finish and hook teardown can
1025    /// run and fail even when zero values are emitted.
1026    ///
1027    /// # Parameters
1028    ///
1029    /// - `state`: Current conversion cursors and output buffer.
1030    ///
1031    /// # Returns
1032    ///
1033    /// Returns `Ok(())` after all decode-finish values have been encoded.
1034    ///
1035    /// # Errors
1036    ///
1037    /// Returns a converter error when decode finish or encode handling fails.
1038    ///
1039    /// # Panics
1040    ///
1041    /// Panics when a decode-finish value cannot be encoded within the
1042    /// capacity reserved by [`Self::max_finish_output_len`].
1043    fn drain_decoder_finish(
1044        &mut self,
1045        state: &mut ConvertState<'_, D::Unit, E::Unit>,
1046    ) -> Result<(), TranscodeConvertErrorOf<D, E>>
1047    where
1048        D::Value: Default,
1049    {
1050        let value_count = self.decode_engine.max_finish_output_len()?;
1051        if value_count == 0 {
1052            // Skip the Vec allocation when the decoder declares no finish
1053            // output. We still call finish() so that
1054            // codec.decode_finish and hooks.finish_hooks both run —
1055            // hooks may do validation or teardown (e.g. checksum
1056            // verification) that can fail even when emitting zero
1057            // values. Passing an empty slice is safe here because the capacity
1058            // check inside finish() accepts required == 0 against any slice.
1059            self.decode_engine.finish(&mut [], 0)?;
1060            return Ok(());
1061        }
1062        // D::Value: Default is required only when value_count > 0. The bound
1063        // remains on the method signature for the general case; stateless
1064        // codecs never reach this branch.
1065        let mut decoded = Vec::new();
1066        try_reserve_vec(&mut decoded, value_count).map_err(|_| TranscodeFailure::allocation_failed())?;
1067        decoded.resize_with(value_count, D::Value::default);
1068        let written = self.decode_engine.finish(&mut decoded, 0)?;
1069        for value in decoded.into_iter().take(written) {
1070            let pending = PendingValue::new(value, 0);
1071            let progress = self.encode_pending(pending, state)?;
1072            assert_reserved_output_drained(
1073                progress,
1074                "converter finish bound must reserve space for decode finish values",
1075            );
1076        }
1077        Ok(())
1078    }
1079
1080    /// Encodes one pending value and applies output/pending state changes.
1081    ///
1082    /// When the target buffer lacks capacity, the value is put back into the
1083    /// pending slot and progress reports
1084    /// [`crate::TranscodeStatus::NeedOutput`].
1085    ///
1086    /// # Parameters
1087    ///
1088    /// - `pending`: Decoded value waiting for target output capacity.
1089    /// - `state`: Current conversion cursors and output buffer.
1090    ///
1091    /// # Returns
1092    ///
1093    /// Returns conversion progress when the value needs more output capacity,
1094    /// or `None` when the value was encoded or skipped by policy.
1095    ///
1096    /// # Errors
1097    ///
1098    /// Returns a converter error when encode hook handling fails.
1099    fn encode_pending(
1100        &mut self,
1101        pending: PendingValue<D::Value>,
1102        state: &mut ConvertState<'_, D::Unit, E::Unit>,
1103    ) -> Result<Option<TranscodeProgress>, TranscodeConvertErrorOf<D, E>> {
1104        let input_index = pending.input_index();
1105        let output_index = state.output_cursor();
1106        let attempt = EncodeAttempt::new(pending.value(), input_index, state.output_mut(), output_index);
1107        let outcome = match self.encode_engine.encode_one(attempt) {
1108            Ok(outcome) => outcome,
1109            Err(error) => {
1110                return Err(TranscodeConvertError::from_encode_error_with_value(
1111                    error,
1112                    pending.into_value(),
1113                ));
1114            }
1115        };
1116        let progress = state.apply_encode_outcome(outcome);
1117        if progress.is_some() {
1118            self.pending.put(pending);
1119        }
1120        Ok(progress)
1121    }
1122}
1123
1124impl<D, E, DH, EH> Default for TranscodeConvertEngine<D, E, DH, EH>
1125where
1126    D: Codec + Default,
1127    E: Codec<Value = D::Value> + Default,
1128    DH: TranscodeDecodeHooks<D> + Default,
1129    EH: TranscodeEncodeHooks<E> + Default,
1130{
1131    /// Creates a default buffered converter engine.
1132    ///
1133    /// # Returns
1134    ///
1135    /// Returns a converter engine constructed from default codecs and hooks.
1136    fn default() -> Self {
1137        Self::new(D::default(), E::default(), DH::default(), EH::default())
1138    }
1139}
1140
1141impl<D, E, DH, EH> Transcoder for TranscodeConvertEngine<D, E, DH, EH>
1142where
1143    D: Codec,
1144    E: Codec<Value = D::Value>,
1145    D::Value: Default,
1146    DH: TranscodeDecodeHooks<D>,
1147    EH: TranscodeEncodeHooks<E>,
1148{
1149    /// Source representation units accepted by the decoder.
1150    type Input = D::Unit;
1151    /// Target representation units emitted by the encoder.
1152    type Output = E::Unit;
1153    /// Source, target, contract, and capacity failures in the pipeline.
1154    type Error = TranscodeConvertErrorOf<D, E>;
1155
1156    /// Returns an upper bound for target units produced from `input_len`
1157    /// units.
1158    ///
1159    /// # Parameters
1160    ///
1161    /// - `input_len`: Number of source units to include in capacity planning.
1162    ///
1163    /// # Returns
1164    ///
1165    /// The target-unit capacity bound, including the applicable retained state.
1166    ///
1167    /// # Errors
1168    ///
1169    /// Returns [`CapacityError`] when a component bound or their sum overflows
1170    /// `usize`.
1171    #[inline]
1172    fn max_transcode_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
1173        TranscodeConvertEngine::max_transcode_output_len(self, input_len)
1174    }
1175
1176    /// Returns an upper bound for target units emitted by finishing retained
1177    /// state.
1178    ///
1179    /// # Returns
1180    ///
1181    /// The target-unit capacity bound, including the applicable retained state.
1182    ///
1183    /// # Errors
1184    ///
1185    /// Returns [`CapacityError`] when a component bound or their sum overflows
1186    /// `usize`.
1187    #[inline]
1188    fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
1189        TranscodeConvertEngine::max_finish_output_len(self)
1190    }
1191
1192    /// Returns an upper bound for target units emitted when resetting stream
1193    /// state.
1194    ///
1195    /// # Returns
1196    ///
1197    /// The target-unit capacity bound, including the applicable retained state.
1198    ///
1199    /// # Errors
1200    ///
1201    /// Returns [`CapacityError`] when a component bound or their sum overflows
1202    /// `usize`.
1203    #[inline]
1204    fn max_reset_output_len(&self) -> Result<usize, CapacityError> {
1205        TranscodeConvertEngine::max_reset_output_len(self)
1206    }
1207
1208    /// Clears retained conversion state and emits target reset output.
1209    ///
1210    /// # Parameters
1211    ///
1212    /// - `output`: Destination for lifecycle output units.
1213    /// - `output_index`: Starting index in `output`.
1214    ///
1215    /// # Returns
1216    ///
1217    /// The number of lifecycle output units written.
1218    ///
1219    /// # Errors
1220    ///
1221    /// Returns the engine error for invalid indices, insufficient capacity,
1222    /// an invalid lifecycle transition, or codec/hook failure. See the inherent
1223    /// [`TranscodeConvertEngine::reset`] method for retry and poisoning
1224    /// semantics.
1225    ///
1226    /// # Panics
1227    ///
1228    /// Panics if a codec/hook output bound fails to reserve enough space for
1229    /// emitted values.
1230    #[inline]
1231    fn reset(&mut self, output: &mut [E::Unit], output_index: usize) -> Result<usize, TranscodeConvertErrorOf<D, E>> {
1232        TranscodeConvertEngine::reset(self, output, output_index)
1233    }
1234
1235    /// Converts source units into target units.
1236    ///
1237    /// # Parameters
1238    ///
1239    /// - `input`: Source units visible to this call.
1240    /// - `input_index`: Starting index in `input`.
1241    /// - `output`: Destination for target units.
1242    /// - `output_index`: Starting index in `output`.
1243    ///
1244    /// # Returns
1245    ///
1246    /// Consumed source units, written target units, and the stopping reason.
1247    ///
1248    /// # Errors
1249    ///
1250    /// Returns an error for invalid indices, a missing reset, a finished or
1251    /// poisoned stream, or codec/hook conversion failure.
1252    #[inline]
1253    fn transcode(
1254        &mut self,
1255        input: &[D::Unit],
1256        input_index: usize,
1257        output: &mut [E::Unit],
1258        output_index: usize,
1259    ) -> Result<TranscodeProgress, TranscodeConvertErrorOf<D, E>> {
1260        TranscodeConvertEngine::transcode(self, input, input_index, output, output_index)
1261    }
1262
1263    /// Transcodes an input segment while marking it as the end of the stream.
1264    ///
1265    /// This forwarding implementation delegates to the engine's EOF-aware
1266    /// operation so the decoder can resolve trailing input using EOF rules.
1267    /// Call `finish` separately to drain retained lifecycle output.
1268    ///
1269    /// # Parameters
1270    ///
1271    /// - `input`: Source units visible to this call.
1272    /// - `input_index`: Starting index in `input`.
1273    /// - `output`: Destination for target units.
1274    /// - `output_index`: Starting index in `output`.
1275    ///
1276    /// # Returns
1277    ///
1278    /// Consumed source units, written target units, and the stopping reason.
1279    ///
1280    /// # Errors
1281    ///
1282    /// Returns an error for invalid indices, a missing reset, a finished or
1283    /// poisoned stream, or codec/hook conversion failure.
1284    #[inline]
1285    fn transcode_eof(
1286        &mut self,
1287        input: &[D::Unit],
1288        input_index: usize,
1289        output: &mut [E::Unit],
1290        output_index: usize,
1291    ) -> Result<TranscodeProgress, TranscodeConvertErrorOf<D, E>> {
1292        TranscodeConvertEngine::transcode_eof(self, input, input_index, output, output_index)
1293    }
1294
1295    /// Finishes retained converter output after EOF.
1296    ///
1297    /// # Parameters
1298    ///
1299    /// - `output`: Destination for lifecycle output units.
1300    /// - `output_index`: Starting index in `output`.
1301    ///
1302    /// # Returns
1303    ///
1304    /// The number of lifecycle output units written.
1305    ///
1306    /// # Errors
1307    ///
1308    /// Returns the engine error for invalid indices, insufficient capacity,
1309    /// an invalid lifecycle transition, or codec/hook failure. See the inherent
1310    /// [`TranscodeConvertEngine::finish`] method for retry and poisoning
1311    /// semantics.
1312    ///
1313    /// # Panics
1314    ///
1315    /// Panics if a codec/hook output bound fails to reserve enough space for
1316    /// emitted values.
1317    #[inline]
1318    fn finish(&mut self, output: &mut [E::Unit], output_index: usize) -> Result<usize, TranscodeConvertErrorOf<D, E>> {
1319        TranscodeConvertEngine::finish(self, output, output_index)
1320    }
1321}
1322
1323impl<D, E, DH, EH> TranscodeConverter for TranscodeConvertEngine<D, E, DH, EH>
1324where
1325    D: Codec,
1326    E: Codec<Value = D::Value>,
1327    D::Value: Default,
1328    DH: TranscodeDecodeHooks<D>,
1329    EH: TranscodeEncodeHooks<E>,
1330{
1331    /// Source codec failures retained in conversion errors.
1332    type DecodeError = D::DecodeError;
1333    /// Target codec failures retained in conversion errors.
1334    type EncodeError = E::EncodeError;
1335    /// Shared logical value transferred between the two codecs.
1336    type Value = D::Value;
1337}