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}