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::TranscodeDecodeEngine`] and a target
11//! [`crate::TranscodeEncodeEngine`] into one unit-to-unit conversion pipeline.
12
13use super::super::internal::{
14 convert_error_of::ConvertErrorOf,
15 convert_state::ConvertState,
16 lifecycle::LifecycleGuard,
17 pending_value::PendingValue,
18 pending_value_slot::PendingValueSlot,
19};
20use super::{
21 transcode_decode_engine::TranscodeDecodeEngine,
22 transcode_encode_engine::TranscodeEncodeEngine,
23};
24use crate::codec::assert_unit_bounds;
25use crate::{
26 CapacityError,
27 Codec,
28 EncodeContext,
29 TranscodeConvertEngineError,
30 TranscodeDecodeEngineError,
31 TranscodeDecodeHooks,
32 TranscodeEncodeEngineError,
33 TranscodeEncodeHooks,
34 TranscodeError,
35 TranscodeProgress,
36 Transcoder,
37};
38
39/// Reusable buffered conversion engine for codec-backed converters.
40///
41/// The engine owns reusable buffered decode and encode engines. It keeps
42/// common converter control flow private: index validation, pending-value
43/// retention, pending flush, decode-error policy dispatch, encode attempts,
44/// output-capacity checks, and [`crate::TranscodeStatus`] reporting.
45///
46/// Use this type to build a streaming converter over two one-value [`Codec`]
47/// implementations that share the same logical value type. Each hot-path step
48/// decodes one source unit sequence into a value, then immediately tries to
49/// encode that value into the target output buffer. If the target buffer lacks
50/// capacity, the decoded value is retained in an internal pending slot and
51/// must be drained before more source input is consumed, preserving output
52/// order across buffer turns.
53///
54/// `TranscodeConvertEngine` is intentionally batch-oriented. Its public
55/// [`Self::transcode`] method drives a source/output buffer loop and reuses the
56/// same unchecked codec and hook primitives as [`crate::TranscodeDecodeEngine`]
57/// and [`crate::TranscodeEncodeEngine`]. It does not call one-value public
58/// transcoders in the hot path.
59///
60/// For strict codec-backed conversion with default decode and encode policies,
61/// use [`crate::CodecTranscodeConverter`]. Use `TranscodeConvertEngine`
62/// directly when either side needs custom malformed-input repair, encode
63/// planning, skipped values, or finish-time output.
64///
65/// The engine follows the same lifecycle as [`crate::Transcoder`]:
66/// `reset → transcode* → finish → reset`. Call [`Self::reset`] before starting
67/// a new logical stream and [`Self::finish`] after EOF once any incomplete
68/// source tail has been handled.
69///
70/// # Example
71///
72/// ```rust
73/// use core::{
74/// convert::Infallible,
75/// num::NonZeroUsize,
76/// };
77/// use qubit_codec::{
78/// Codec,
79/// DecodeContext,
80/// DecodeFailure,
81/// EncodeContext,
82/// EncodeOutcome,
83/// TranscodeConvertEngine,
84/// TranscodeDecodeHooks,
85/// TranscodeEncodeHooks,
86/// TranscodeStatus,
87/// };
88///
89/// #[derive(Clone, Copy)]
90/// struct SourceCodec;
91///
92/// #[derive(Clone, Copy)]
93/// struct TargetCodec;
94///
95/// impl Codec for SourceCodec {
96/// type Value = u8;
97/// type Unit = u8;
98/// type DecodeError = Infallible;
99/// type EncodeError = Infallible;
100///
101/// const MIN_UNITS_PER_VALUE: NonZeroUsize = NonZeroUsize::MIN;
102/// const MAX_UNITS_PER_VALUE: NonZeroUsize = NonZeroUsize::MIN;
103///
104/// unsafe fn decode(
105/// &mut self,
106/// input: &[u8],
107/// index: usize,
108/// ) -> Result<(u8, NonZeroUsize), DecodeFailure<Self::DecodeError>> {
109/// Ok((input[index].wrapping_add(1), NonZeroUsize::MIN))
110/// }
111///
112/// unsafe fn encode(
113/// &mut self,
114/// value: &u8,
115/// output: &mut [u8],
116/// index: usize,
117/// ) -> Result<NonZeroUsize, Self::EncodeError> {
118/// output[index] = *value;
119/// Ok(NonZeroUsize::MIN)
120/// }
121/// }
122///
123/// impl Codec for TargetCodec {
124/// type Value = u8;
125/// type Unit = u8;
126/// type DecodeError = Infallible;
127/// type EncodeError = Infallible;
128///
129/// const MIN_UNITS_PER_VALUE: NonZeroUsize = NonZeroUsize::MIN;
130/// const MAX_UNITS_PER_VALUE: NonZeroUsize = NonZeroUsize::MIN;
131///
132/// unsafe fn decode(
133/// &mut self,
134/// input: &[u8],
135/// index: usize,
136/// ) -> Result<(u8, NonZeroUsize), DecodeFailure<Self::DecodeError>> {
137/// Ok((input[index], NonZeroUsize::MIN))
138/// }
139///
140/// unsafe fn encode(
141/// &mut self,
142/// value: &u8,
143/// output: &mut [u8],
144/// index: usize,
145/// ) -> Result<NonZeroUsize, Self::EncodeError> {
146/// output[index] = *value;
147/// Ok(NonZeroUsize::MIN)
148/// }
149/// }
150///
151/// struct StrictDecodeHooks;
152///
153/// impl TranscodeDecodeHooks<SourceCodec> for StrictDecodeHooks {
154/// type Error = Infallible;
155///
156/// fn handle_invalid_decode(
157/// &mut self,
158/// _codec: &mut SourceCodec,
159/// error: Infallible,
160/// _consumed: Option<NonZeroUsize>,
161/// _context: DecodeContext,
162/// ) -> Result<qubit_codec::DecodeInvalidAction<u8>, Self::Error> {
163/// match error {}
164/// }
165/// }
166///
167/// struct StrictEncodeHooks;
168///
169/// impl TranscodeEncodeHooks<TargetCodec> for StrictEncodeHooks {
170/// type Error = Infallible;
171///
172/// fn encode_value(
173/// &mut self,
174/// codec: &mut TargetCodec,
175/// context: EncodeContext<'_, u8, u8>,
176/// ) -> Result<EncodeOutcome, Self::Error> {
177/// let required = TargetCodec::MAX_UNITS_PER_VALUE;
178/// if context.available_output() < required.get() {
179/// return Ok(EncodeOutcome::need_output(required));
180/// }
181/// let (value, _, output, output_index) = context.into_parts();
182/// let written = unsafe { codec.encode(value, output, output_index) }
183/// .map(NonZeroUsize::get)
184/// .unwrap();
185/// Ok(EncodeOutcome::consumed(written))
186/// }
187/// }
188///
189/// let mut engine = TranscodeConvertEngine::new(
190/// SourceCodec,
191/// TargetCodec,
192/// StrictDecodeHooks,
193/// StrictEncodeHooks,
194/// );
195/// let input = [1_u8, 2, 3];
196/// let mut output = [0_u8; 2];
197///
198/// let progress = engine.transcode(&input, 0, &mut output, 0)?;
199/// match progress.status() {
200/// TranscodeStatus::NeedOutput { output_index, .. } => {
201/// assert_eq!(2, output_index);
202/// assert_eq!([2, 3], output);
203/// // Drain `output[..output_index]`, then resume at
204/// // `progress.read()` with fresh output capacity.
205/// }
206/// TranscodeStatus::Complete => unreachable!("output is intentionally short"),
207/// TranscodeStatus::NeedInput { .. } => unreachable!("input is complete"),
208/// }
209/// # Ok::<(), qubit_codec::TranscodeError<qubit_codec::TranscodeConvertEngineError<
210/// # qubit_codec::TranscodeDecodeEngineError<Infallible, Infallible>,
211/// # qubit_codec::TranscodeEncodeEngineError<Infallible, Infallible>,
212/// # >>>(())
213/// ```
214///
215/// # Type Parameters
216///
217/// - `D`: Source-side decoder codec.
218/// - `E`: Target-side encoder codec.
219/// - `DH`: Source-side decode hooks.
220/// - `EH`: Target-side encode hooks.
221#[derive(Debug)]
222pub struct TranscodeConvertEngine<D, E, DH, EH>
223where
224 D: Codec,
225 E: Codec<Value = D::Value>,
226 DH: TranscodeDecodeHooks<D>,
227 EH: TranscodeEncodeHooks<E>,
228{
229 /// Source-side buffered decoder engine.
230 decode_engine: TranscodeDecodeEngine<D, DH>,
231 /// Target-side buffered encoder engine.
232 encode_engine: TranscodeEncodeEngine<E, EH>,
233 /// Decoded value waiting for target output capacity.
234 pending: PendingValueSlot<D::Value>,
235 /// Debug-only guard for the `reset → transcode* → finish` lifecycle.
236 /// Zero-sized in release builds. The converter owns its own guard rather
237 /// than delegating to the inner decode/encode engines, because lifecycle
238 /// events here describe the converter as a whole.
239 lifecycle: LifecycleGuard,
240}
241
242impl<D, E, DH, EH> TranscodeConvertEngine<D, E, DH, EH>
243where
244 D: Codec,
245 E: Codec<Value = D::Value>,
246 DH: TranscodeDecodeHooks<D>,
247 EH: TranscodeEncodeHooks<E>,
248{
249 /// Creates a buffered converter engine.
250 ///
251 /// The caller supplies decode hooks and encode hooks directly.
252 ///
253 /// # Parameters
254 ///
255 /// - `decoder`: Low-level codec used for source decoding.
256 /// - `encoder`: Low-level codec used for target encoding.
257 /// - `decode_hooks`: Decode-side policy hooks.
258 /// - `encode_hooks`: Encode-side policy hooks.
259 ///
260 /// # Returns
261 ///
262 /// Returns a buffered converter engine.
263 ///
264 /// # Panics
265 ///
266 /// In debug builds, panics when either codec violates the
267 /// [`Codec::MIN_UNITS_PER_VALUE`] / [`Codec::MAX_UNITS_PER_VALUE`] ordering
268 /// invariant. Release builds skip this check because the invariant is the
269 /// responsibility of each [`Codec`] implementation.
270 #[inline]
271 #[must_use]
272 pub fn new(
273 decoder: D,
274 encoder: E,
275 decode_hooks: DH,
276 encode_hooks: EH,
277 ) -> Self {
278 assert_unit_bounds::<D>();
279 assert_unit_bounds::<E>();
280 Self {
281 decode_engine: TranscodeDecodeEngine::new(decoder, decode_hooks),
282 encode_engine: TranscodeEncodeEngine::new(encoder, encode_hooks),
283 pending: PendingValueSlot::empty(),
284 lifecycle: LifecycleGuard::new(),
285 }
286 }
287
288 /// Returns an upper bound for target units produced from `input_len` units.
289 ///
290 /// The bound sums three parts: any retained pending value, the maximum
291 /// decoded values from the source side, and the maximum target units for
292 /// those values on the encode side.
293 ///
294 /// # Parameters
295 ///
296 /// - `input_len`: Number of source units the caller plans to convert.
297 ///
298 /// # Returns
299 ///
300 /// Returns a conservative upper bound, or a capacity error on arithmetic
301 /// overflow.
302 #[must_use = "capacity planning can fail on overflow"]
303 pub fn max_output_len(
304 &self,
305 input_len: usize,
306 ) -> Result<usize, CapacityError> {
307 let pending_units = self.pending_output_len()?;
308 let decoded_values = self.decode_engine.max_output_len(input_len)?;
309 let converted_units =
310 self.encode_engine.max_output_len(decoded_values)?;
311 converted_units
312 .checked_add(pending_units)
313 .ok_or(CapacityError::OutputLengthOverflow)
314 }
315
316 /// Returns the maximum target units emitted when resetting stream state.
317 ///
318 /// Covers decode-side reset values (encoded to target units) plus
319 /// encode-side reset units. Most codecs are stateless and return `0`
320 /// for [`Codec::MAX_DECODE_RESET_VALUES`]; in that case this equals the
321 /// encode reset bound only.
322 ///
323 /// # Returns
324 ///
325 /// Returns the combined decode-reset and encode-reset output bound, or a
326 /// capacity error on arithmetic overflow.
327 #[must_use = "capacity planning can fail on overflow"]
328 pub fn max_reset_output_len(&self) -> Result<usize, CapacityError> {
329 let decode_reset_units = self
330 .encode_engine
331 .max_output_len(D::MAX_DECODE_RESET_VALUES)?;
332 let encode_reset_units = E::MAX_ENCODE_RESET_UNITS;
333 decode_reset_units
334 .checked_add(encode_reset_units)
335 .ok_or(CapacityError::OutputLengthOverflow)
336 }
337
338 /// Returns the maximum target units emitted by finishing retained state.
339 ///
340 /// The bound covers a retained pending value, decode-side finish values
341 /// (encoded to target units), and encode-side finish units.
342 ///
343 /// # Returns
344 ///
345 /// Returns the combined pending, decode-finish, and encode-finish output
346 /// bound, or a capacity error on arithmetic overflow.
347 #[must_use = "capacity planning can fail on overflow"]
348 pub fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
349 let pending_units = self.pending_output_len()?;
350 let decoder_finish_values =
351 self.decode_engine.max_finish_output_len()?;
352 let decoder_finish_units =
353 self.encode_engine.max_output_len(decoder_finish_values)?;
354 let encoder_finish_units =
355 self.encode_engine.max_finish_output_len()?;
356 let pending_and_decoder = pending_units
357 .checked_add(decoder_finish_units)
358 .ok_or(CapacityError::OutputLengthOverflow)?;
359 pending_and_decoder
360 .checked_add(encoder_finish_units)
361 .ok_or(CapacityError::OutputLengthOverflow)
362 }
363
364 /// Converts source units into target units.
365 ///
366 /// The engine drains any retained pending value before consuming new input.
367 /// Each loop iteration decodes one source value and immediately attempts to
368 /// encode it. Conversion stops when the input tail is incomplete, when the
369 /// output buffer cannot hold the next encoded value, or when the visible
370 /// input is exhausted.
371 ///
372 /// # Parameters
373 ///
374 /// - `input`: Complete input unit slice visible to the converter.
375 /// - `input_index`: Absolute input index where conversion starts.
376 /// - `output`: Complete output unit slice visible to the converter.
377 /// - `output_index`: Absolute output index where writing starts.
378 ///
379 /// # Returns
380 ///
381 /// Returns conversion progress describing input units consumed, target
382 /// units written, and why conversion stopped.
383 ///
384 /// # Errors
385 ///
386 /// Returns hook errors when indices are invalid or concrete conversion
387 /// fails. Invalid output indices are reported through the encode-side
388 /// error path.
389 pub fn transcode(
390 &mut self,
391 input: &[D::Unit],
392 input_index: usize,
393 output: &mut [E::Unit],
394 output_index: usize,
395 ) -> Result<TranscodeProgress, ConvertErrorOf<D, E, DH, EH>> {
396 self.lifecycle.on_transcode();
397 TranscodeError::ensure_transcode_indices(
398 input.len(),
399 input_index,
400 output.len(),
401 output_index,
402 )?;
403
404 let mut state =
405 ConvertState::new(input, input_index, output, output_index);
406
407 // A retained decoded value must be written before consuming more input,
408 // otherwise callers could observe output reordered across buffer turns.
409 if let Some(progress) = self.drain_pending(&mut state)? {
410 return Ok(progress);
411 }
412
413 let min_input_units = D::MIN_UNITS_PER_VALUE;
414 while state.has_input() {
415 let available = state.available_input();
416 if available < min_input_units.get() {
417 return Ok(
418 state.need_input_progress(min_input_units, available)
419 );
420 }
421
422 let previous_read = state.read();
423 // Each hot-path step decodes one source value and immediately tries
424 // to encode it, preserving backpressure at the target output.
425 if let Some(progress) = self.convert_next(&mut state)? {
426 return Ok(progress);
427 }
428 debug_assert!(
429 state.read() > previous_read,
430 "TranscodeConvertEngine conversion step must consume input or stop",
431 );
432 }
433
434 Ok(state.complete_progress())
435 }
436
437 /// Finishes retained output after EOF.
438 ///
439 /// Finalization drains a pending decoded value first, then lets the
440 /// source-side decode hooks emit final values, encodes those values through
441 /// the target-side encode hooks, and finally finishes target-side encode
442 /// hook state. The decode-finish value buffer used for this cold path
443 /// requires `D::Value: Default`; the normal [`Self::transcode`] loop does
444 /// not.
445 ///
446 /// # Parameters
447 ///
448 /// - `output`: Complete output unit slice visible to the converter.
449 /// - `output_index`: Absolute output index where writing starts.
450 ///
451 /// # Returns
452 ///
453 /// Returns the number of target units written during finalization.
454 ///
455 /// # Errors
456 ///
457 /// Returns a converter error when output capacity checks fail or when
458 /// hook finalization fails.
459 ///
460 /// # Panics
461 ///
462 /// Panics in debug builds when a retained pending value or decode-finish
463 /// value cannot be encoded within the capacity reserved by
464 /// [`Self::max_finish_output_len`].
465 pub fn finish(
466 &mut self,
467 output: &mut [E::Unit],
468 output_index: usize,
469 ) -> Result<usize, ConvertErrorOf<D, E, DH, EH>>
470 where
471 D::Value: Default,
472 {
473 self.lifecycle.on_finish_attempt();
474 let required = self.max_finish_output_len()?;
475 TranscodeError::ensure_output_capacity(
476 output.len(),
477 output_index,
478 required,
479 )?;
480
481 let empty_input: &[D::Unit] = &[];
482 let mut state = ConvertState::new(empty_input, 0, output, output_index);
483 // Finish keeps the same priority as transcode: output any retained
484 // decoded value before asking source-side hooks for final values.
485 if self.drain_pending(&mut state)?.is_some() {
486 unreachable!(
487 "converter finish bound must reserve space for pending values"
488 );
489 }
490
491 // Source-side finish may emit one or more final values. Drain them into
492 // the target encoder before finishing target-side hook state.
493 self.drain_decoder_finish(&mut state)?;
494
495 let output_cursor = state.output_cursor();
496 let written = self
497 .encode_engine
498 .finish(state.output_mut(), output_cursor)
499 .map_err(|error| {
500 error.map_domain(TranscodeConvertEngineError::encode)
501 })?;
502 state.advance_output(written);
503 self.lifecycle.on_finish_success();
504 Ok(state.written())
505 }
506
507 /// Clears retained conversion state, runs before-reset hooks, and emits
508 /// stream-start encode output.
509 ///
510 /// Reset clears any retained pending value, drains decode-side reset
511 /// values through the target encoder, then emits encode-side reset units.
512 /// The caller must provide enough output capacity for
513 /// [`Self::max_reset_output_len`].
514 ///
515 /// # Parameters
516 ///
517 /// - `output`: Complete output unit slice visible to the converter.
518 /// - `output_index`: Absolute output index where writing starts.
519 ///
520 /// # Returns
521 ///
522 /// Returns the number of target units written while resetting stream state.
523 ///
524 /// # Errors
525 ///
526 /// Returns a converter error if reset validation or target reset output
527 /// emission fails.
528 ///
529 /// # Panics
530 ///
531 /// Panics in debug builds when decode-reset values cannot be encoded
532 /// within the capacity reserved by [`Self::max_reset_output_len`].
533 pub fn reset(
534 &mut self,
535 output: &mut [E::Unit],
536 output_index: usize,
537 ) -> Result<usize, ConvertErrorOf<D, E, DH, EH>>
538 where
539 D::Value: Default,
540 {
541 self.lifecycle.on_reset();
542 let required = self.max_reset_output_len()?;
543 TranscodeError::ensure_output_capacity(
544 output.len(),
545 output_index,
546 required,
547 )?;
548
549 self.pending.clear();
550
551 // Source-side reset may emit stream-start values (such as a BOM) that
552 // must be piped through the target encoder before any encoder-owned
553 // reset output. `max_reset_output_len` already reserves space for both
554 // halves of the pipeline, so encode_pending should never report
555 // `NeedOutput` here.
556 let empty_input: &[D::Unit] = &[];
557 let mut state = ConvertState::new(empty_input, 0, output, output_index);
558 self.drain_decoder_reset(&mut state)?;
559
560 let output_cursor = state.output_cursor();
561 let encoder_written = self
562 .encode_engine
563 .reset(state.output_mut(), output_cursor)
564 .map_err(|error| {
565 error.map_domain(TranscodeConvertEngineError::encode)
566 })?;
567 state.advance_output(encoder_written);
568 Ok(state.written())
569 }
570
571 /// Drains source-side decode reset output and encodes emitted reset
572 /// values.
573 ///
574 /// Stateless decoders still call [`TranscodeDecodeEngine::reset`] so hook
575 /// teardown side effects run even when no reset values are emitted.
576 ///
577 /// # Parameters
578 ///
579 /// - `state`: Current conversion cursors and output buffer.
580 ///
581 /// # Returns
582 ///
583 /// Returns `Ok(())` after all decode-reset values have been encoded.
584 ///
585 /// # Errors
586 ///
587 /// Returns a converter error when decode reset or encode reset handling
588 /// fails.
589 ///
590 /// # Panics
591 ///
592 /// Panics when a decode-reset value cannot be encoded within the capacity
593 /// reserved by [`Self::max_reset_output_len`].
594 fn drain_decoder_reset(
595 &mut self,
596 state: &mut ConvertState<'_, D::Unit, E::Unit>,
597 ) -> Result<(), ConvertErrorOf<D, E, DH, EH>>
598 where
599 D::Value: Default,
600 {
601 let value_count = D::MAX_DECODE_RESET_VALUES;
602 if value_count == 0 {
603 // Stateless decoder: still call decode_reset so codecs whose
604 // hooks own teardown side effects (e.g. clearing accumulators)
605 // run them. The empty slice is safe because the capacity check
606 // inside `reset` accepts `required == 0` against any slice.
607 self.decode_engine.reset(&mut [], 0).map_err(|error| {
608 error.map_domain(TranscodeConvertEngineError::decode)
609 })?;
610 return Ok(());
611 }
612 // `D::Value: Default` is only consulted when the decoder declares
613 // reset output. Stateless codecs never reach this branch.
614 let mut reset_values: Vec<D::Value> =
615 (0..value_count).map(|_| D::Value::default()).collect();
616 let written = self.decode_engine.reset(&mut reset_values, 0).map_err(
617 |error| error.map_domain(TranscodeConvertEngineError::decode),
618 )?;
619 for value in reset_values.into_iter().take(written) {
620 let pending = PendingValue::new(value, 0);
621 if self.encode_pending(pending, state)?.is_some() {
622 unreachable!(
623 "converter reset bound must reserve space for decode reset values"
624 );
625 }
626 }
627 Ok(())
628 }
629
630 /// Converts one source value from the current state cursors.
631 ///
632 /// Decodes one value through the source engine, then immediately attempts
633 /// to encode it through the target engine.
634 ///
635 /// # Parameters
636 ///
637 /// - `state`: Current conversion cursors and output buffer.
638 ///
639 /// # Returns
640 ///
641 /// Returns conversion progress when the step stops early, or `None` when
642 /// the value was fully consumed and encoded.
643 ///
644 /// # Errors
645 ///
646 /// Returns a converter error when decode or encode handling fails.
647 #[inline(always)]
648 fn convert_next(
649 &mut self,
650 state: &mut ConvertState<'_, D::Unit, E::Unit>,
651 ) -> Result<Option<TranscodeProgress>, ConvertErrorOf<D, E, DH, EH>> {
652 let step = self
653 .decode_engine
654 .decode_step(state.input(), state.decode_context())
655 .map_err(|error| {
656 error.map_domain(TranscodeConvertEngineError::decode)
657 })?;
658 state.apply_decode_step(step, |pending, state| {
659 self.encode_pending(pending, state)
660 })
661 }
662
663 /// Returns the output bound for the retained pending value.
664 ///
665 /// # Returns
666 ///
667 /// Returns the maximum target units needed to encode the pending value,
668 /// or `0` when no value is retained. Returns a capacity error when hook
669 /// planning overflows.
670 #[inline(always)]
671 fn pending_output_len(&self) -> Result<usize, CapacityError> {
672 self.pending.max_output_len(&self.encode_engine)
673 }
674
675 /// Writes a retained decoded value before new input is consumed.
676 ///
677 /// # Parameters
678 ///
679 /// - `state`: Current conversion cursors and output buffer.
680 ///
681 /// # Returns
682 ///
683 /// Returns conversion progress when the pending value needs more output
684 /// capacity, or `None` when the pending value was fully encoded.
685 ///
686 /// # Errors
687 ///
688 /// Returns a converter error when encode handling fails.
689 #[inline(always)]
690 fn drain_pending(
691 &mut self,
692 state: &mut ConvertState<'_, D::Unit, E::Unit>,
693 ) -> Result<Option<TranscodeProgress>, ConvertErrorOf<D, E, DH, EH>> {
694 let Some(pending) = self.pending.take() else {
695 return Ok(None);
696 };
697 self.encode_pending(pending, state)
698 }
699
700 /// Drains source-side decode finish output and encodes emitted final
701 /// values.
702 ///
703 /// When the decoder declares no finish output, still calls
704 /// [`TranscodeDecodeEngine::finish`] so codec flush and hook teardown can
705 /// run and fail even when zero values are emitted.
706 ///
707 /// # Parameters
708 ///
709 /// - `state`: Current conversion cursors and output buffer.
710 ///
711 /// # Returns
712 ///
713 /// Returns `Ok(())` after all decode-finish values have been encoded.
714 ///
715 /// # Errors
716 ///
717 /// Returns a converter error when decode finish or encode handling fails.
718 ///
719 /// # Panics
720 ///
721 /// Panics when a decode-finish value cannot be encoded within the
722 /// capacity reserved by [`Self::max_finish_output_len`].
723 fn drain_decoder_finish(
724 &mut self,
725 state: &mut ConvertState<'_, D::Unit, E::Unit>,
726 ) -> Result<(), ConvertErrorOf<D, E, DH, EH>>
727 where
728 D::Value: Default,
729 {
730 let value_count = self.decode_engine.max_finish_output_len()?;
731 if value_count == 0 {
732 // Skip the Vec allocation when the decoder declares no finish
733 // output. We still call finish() so that
734 // codec.decode_flush and hooks.finish_hooks both run —
735 // hooks may do validation or teardown (e.g. checksum
736 // verification) that can fail even when emitting zero
737 // values. Passing an empty slice is safe here because the capacity
738 // check inside finish() accepts required == 0 against any slice.
739 self.decode_engine.finish(&mut [], 0).map_err(|error| {
740 error.map_domain(TranscodeConvertEngineError::decode)
741 })?;
742 return Ok(());
743 }
744 // D::Value: Default is required only when value_count > 0. The bound
745 // remains on the method signature for the general case; stateless
746 // codecs never reach this branch.
747 let mut decoded: Vec<D::Value> =
748 (0..value_count).map(|_| D::Value::default()).collect();
749 let written =
750 self.decode_engine
751 .finish(&mut decoded, 0)
752 .map_err(|error| {
753 error.map_domain(TranscodeConvertEngineError::decode)
754 })?;
755 for value in decoded.into_iter().take(written) {
756 let pending = PendingValue::new(value, 0);
757 if self.encode_pending(pending, state)?.is_some() {
758 unreachable!(
759 "converter finish bound must reserve space for decode finish values"
760 );
761 }
762 }
763 Ok(())
764 }
765
766 /// Encodes one pending value and applies output/pending state changes.
767 ///
768 /// When the target buffer lacks capacity, the value is put back into the
769 /// pending slot and progress reports
770 /// [`crate::TranscodeStatus::NeedOutput`].
771 ///
772 /// # Parameters
773 ///
774 /// - `pending`: Decoded value waiting for target output capacity.
775 /// - `state`: Current conversion cursors and output buffer.
776 ///
777 /// # Returns
778 ///
779 /// Returns conversion progress when the value needs more output capacity,
780 /// or `None` when the value was fully encoded.
781 ///
782 /// # Errors
783 ///
784 /// Returns a converter error when encode hook handling fails.
785 fn encode_pending(
786 &mut self,
787 pending: PendingValue<D::Value>,
788 state: &mut ConvertState<'_, D::Unit, E::Unit>,
789 ) -> Result<Option<TranscodeProgress>, ConvertErrorOf<D, E, DH, EH>> {
790 let input_index = pending.input_index();
791 let output_index = state.output_cursor();
792 let context = EncodeContext::new(
793 pending.value(),
794 input_index,
795 state.output_mut(),
796 output_index,
797 );
798 let outcome =
799 self.encode_engine.encode_one(context).map_err(|error| {
800 TranscodeError::domain(TranscodeConvertEngineError::encode(
801 TranscodeEncodeEngineError::hook(error),
802 ))
803 })?;
804 let progress = state.apply_encode_outcome(outcome);
805 if progress.is_some() {
806 self.pending.put(pending);
807 }
808 Ok(progress)
809 }
810}
811
812impl<D, E, DH, EH> Default for TranscodeConvertEngine<D, E, DH, EH>
813where
814 D: Codec + Default,
815 E: Codec<Value = D::Value> + Default,
816 DH: TranscodeDecodeHooks<D> + Default,
817 EH: TranscodeEncodeHooks<E> + Default,
818{
819 /// Creates a default buffered converter engine.
820 ///
821 /// # Returns
822 ///
823 /// Returns a converter engine constructed from default codecs and hooks.
824 #[inline(always)]
825 fn default() -> Self {
826 Self::new(D::default(), E::default(), DH::default(), EH::default())
827 }
828}
829
830impl<D, E, DH, EH> Transcoder<D::Unit, E::Unit>
831 for TranscodeConvertEngine<D, E, DH, EH>
832where
833 D: Codec,
834 E: Codec<Value = D::Value>,
835 D::Value: Default,
836 DH: TranscodeDecodeHooks<D>,
837 EH: TranscodeEncodeHooks<E>,
838{
839 type Error = TranscodeConvertEngineError<
840 TranscodeDecodeEngineError<D::DecodeError, DH::Error>,
841 TranscodeEncodeEngineError<E::EncodeError, EH::Error>,
842 >;
843
844 /// Returns an upper bound for target units produced from `input_len`
845 /// units.
846 #[inline(always)]
847 fn max_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
848 TranscodeConvertEngine::max_output_len(self, input_len)
849 }
850
851 /// Returns an upper bound for target units emitted by finishing retained
852 /// state.
853 #[inline(always)]
854 fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
855 TranscodeConvertEngine::max_finish_output_len(self)
856 }
857
858 /// Returns an upper bound for target units emitted when resetting stream
859 /// state.
860 #[inline(always)]
861 fn max_reset_output_len(&self) -> Result<usize, CapacityError> {
862 TranscodeConvertEngine::max_reset_output_len(self)
863 }
864
865 /// Clears retained conversion state and emits target reset output.
866 #[inline(always)]
867 fn reset(
868 &mut self,
869 output: &mut [E::Unit],
870 output_index: usize,
871 ) -> Result<usize, TranscodeError<Self::Error>> {
872 TranscodeConvertEngine::reset(self, output, output_index)
873 }
874
875 /// Converts source units into target units.
876 #[inline(always)]
877 fn transcode(
878 &mut self,
879 input: &[D::Unit],
880 input_index: usize,
881 output: &mut [E::Unit],
882 output_index: usize,
883 ) -> Result<TranscodeProgress, TranscodeError<Self::Error>> {
884 TranscodeConvertEngine::transcode(
885 self,
886 input,
887 input_index,
888 output,
889 output_index,
890 )
891 }
892
893 /// Finishes retained converter output after EOF.
894 #[inline(always)]
895 fn finish(
896 &mut self,
897 output: &mut [E::Unit],
898 output_index: usize,
899 ) -> Result<usize, TranscodeError<Self::Error>> {
900 TranscodeConvertEngine::finish(self, output, output_index)
901 }
902}