Skip to main content

qubit_codec/transcode/
transcoder.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// =============================================================================
8use super::capacity_error::CapacityError;
9use super::transcode_failure::TranscodeFailure;
10use super::transcode_progress::TranscodeProgress;
11use super::transcode_status::TranscodeStatus;
12
13/// Validates one-shot transcode progress and returns completed output length.
14///
15/// Keeping progress classification independent of the concrete transcoder
16/// ensures every implementation receives identical trailing-input and
17/// streaming-stop handling.
18///
19/// # Parameters
20///
21/// - `progress`: Progress returned by the streaming transcode phase.
22/// - `input_len`: Total complete-input length supplied to the one-shot call.
23/// - `output_index`: Absolute output index used for the transcode phase.
24/// - `output_len`: Total output slice length.
25///
26/// # Returns
27///
28/// Returns the number of output units written when progress is complete.
29///
30/// # Errors
31///
32/// Returns a trailing-input, incomplete-input, or insufficient-output failure
33/// when progress reports a non-complete one-shot result.
34///
35/// # Panics
36///
37/// Panics when the transcoder reports progress inconsistent with the supplied
38/// input and output bounds.
39fn complete_progress_written(
40    progress: TranscodeProgress,
41    input_len: usize,
42    output_index: usize,
43    output_len: usize,
44) -> Result<usize, TranscodeFailure> {
45    assert!(
46        progress
47            .validate(0, input_len, output_index, output_len.saturating_sub(output_index),)
48            .is_ok(),
49        "Transcoder::transcode returned invalid progress",
50    );
51    match progress.status() {
52        TranscodeStatus::Complete => Ok(progress.written()),
53        TranscodeStatus::NeedOutput { required } => Err(TranscodeFailure::insufficient_output(
54            output_index + progress.written(),
55            required.get(),
56            output_len - output_index - progress.written(),
57        )),
58        TranscodeStatus::NeedInput { required } => Err(TranscodeFailure::incomplete_input(
59            progress.read(),
60            required.get(),
61            input_len - progress.read(),
62        )),
63    }
64}
65
66/// Adds two independent output-capacity bounds.
67///
68/// # Parameters
69///
70/// - `first`: First output-capacity bound.
71/// - `second`: Second output-capacity bound.
72///
73/// # Returns
74///
75/// Returns the sum of both bounds.
76///
77/// # Errors
78///
79/// Returns [`CapacityError::OutputLengthOverflow`] when the sum overflows.
80fn add_output_bounds(first: usize, second: usize) -> Result<usize, CapacityError> {
81    first.checked_add(second).ok_or(CapacityError::OutputLengthOverflow)
82}
83
84/// Adds the reset, transcode, and finish output-capacity bounds.
85///
86/// # Parameters
87///
88/// - `reset`: Reset-phase output-capacity bound.
89/// - `transcode`: Streaming transcode-phase output-capacity bound.
90/// - `finish`: Finish-phase output-capacity bound.
91///
92/// # Returns
93///
94/// Returns the complete lifecycle output-capacity bound.
95///
96/// # Errors
97///
98/// Returns [`CapacityError::OutputLengthOverflow`] when either addition
99/// overflows.
100fn sum_output_bounds(reset: usize, transcode: usize, finish: usize) -> Result<usize, CapacityError> {
101    let before_finish = add_output_bounds(reset, transcode)?;
102    add_output_bounds(before_finish, finish)
103}
104
105/// Converts one logical stream of input units into one logical stream of output
106/// units.
107///
108/// `transcode` is the main streaming API. It transforms a provided input
109/// segment and writes as much output as available buffer space allows. A
110/// successful `transcode` call must stop with exactly one of three meanings:
111/// all visible input was consumed (`Complete`), an incomplete tail remains
112/// caller-owned (`NeedInput`), or output capacity prevented further progress
113/// (`NeedOutput`).
114///
115/// A transcoder instance has a simple lifecycle:
116///
117/// 1. A newly created instance is uninitialized; call [`Transcoder::reset`]
118///    before the first stream. A reset instance is ready for a new logical
119///    stream.
120/// 2. Call [`Transcoder::transcode`] zero or more times while input is
121///    available.
122/// 3. Preserve any tail reported by [`crate::TranscodeStatus::NeedInput`] in
123///    the caller-owned input buffer.
124/// 4. Call [`Transcoder::finish`] after the caller knows no more input remains
125///    and has handled any incomplete tail. Size this final output with
126///    [`Transcoder::max_finish_output_len`].
127/// 5. After [`Transcoder::finish`] succeeds, call [`Transcoder::reset`] with a
128///    buffer sized by [`Transcoder::max_reset_output_len`] before starting
129///    another logical stream with the same instance.
130///
131/// The method is suitable for:
132/// - pull-style consumers that call conversion repeatedly as buffers arrive;
133/// - bounded output sinks that use `NeedOutput` progress during `transcode`;
134/// - stateless and stateful codecs that all return progress-oriented stopping
135///   reasons.
136///
137/// `finish` finalizes retained state only; it does not receive source input and
138/// does not reinterpret a tail previously reported by `NeedInput`. For
139/// codec-backed decoders, streaming uses
140/// [`Codec::decode`](crate::Codec::decode) and an explicit EOF call may use
141/// [`Codec::decode_eof`](crate::Codec::decode_eof)
142/// through [`Transcoder::transcode_eof`]. If a format needs EOF-aware
143/// maximal-munch parsing or must delay whether a prefix is complete until the
144/// next chunk or EOF, implement that policy in the codec or override
145/// `transcode_eof`.
146///
147/// `Transcoder` is intentionally independent from any charset
148/// semantics:
149///
150/// - Use `Transcoder` directly for custom, policy-free unit transforms.
151/// - Use `Transcoder` when you want to own malformed/unmappable decisions at
152///   the call site.
153///
154/// # Examples
155///
156/// Streaming byte-to-word decoding:
157///
158/// ```rust
159/// use core::convert::Infallible;
160/// use core::num::NonZeroUsize;
161///
162/// use qubit_codec::CapacityError;
163/// use qubit_codec::TranscodeDecodeError;
164/// use qubit_codec::TranscodeFailure;
165/// use qubit_codec::TranscodeProgress;
166/// use qubit_codec::TranscodeStatus;
167/// use qubit_codec::Transcoder;
168///
169/// #[derive(Default)]
170/// struct U16BeBytesDecoder;
171///
172/// impl Transcoder for U16BeBytesDecoder {
173///     type Input = u8;
174///     type Output = u16;
175///     type Error = TranscodeDecodeError<Infallible>;
176///
177///     fn max_transcode_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
178///         Ok(input_len / 2)
179///     }
180///
181///     fn reset(
182///         &mut self,
183///         output: &mut [u16],
184///         output_index: usize,
185///     ) -> Result<usize, Self::Error> {
186///         TranscodeFailure::ensure_output_index(output.len(), output_index)?;
187///         Ok(0)
188///     }
189///
190///     fn transcode(
191///         &mut self,
192///         input: &[u8],
193///         input_index: usize,
194///         output: &mut [u16],
195///         output_index: usize,
196///     ) -> Result<TranscodeProgress, Self::Error> {
197///         TranscodeFailure::ensure_transcode_indices(
198///             input.len(),
199///             input_index,
200///             output.len(),
201///             output_index,
202///         )?;
203///
204///         let mut read = 0;
205///         let mut written = 0;
206///         while input_index + read + 1 < input.len() {
207///             if output_index + written == output.len() {
208///                 let status = TranscodeStatus::NeedOutput {
209///                     required: NonZeroUsize::MIN,
210///                 };
211///                 return Ok(TranscodeProgress::new(status, read, written));
212///             }
213///             let high = input[input_index + read] as u16;
214///             let low = input[input_index + read + 1] as u16;
215///             output[output_index + written] = (high << 8) | low;
216///             read += 2;
217///             written += 1;
218///         }
219///         if input_index + read == input.len() {
220///             Ok(TranscodeProgress::complete(read, written))
221///         } else {
222///             let status = TranscodeStatus::NeedInput {
223///                 required: NonZeroUsize::new(2).expect("two is non-zero"),
224///             };
225///             Ok(TranscodeProgress::new(status, read, written))
226///         }
227///     }
228///
229///     fn finish(
230///         &mut self,
231///         output: &mut [u16],
232///         output_index: usize,
233///     ) -> Result<usize, Self::Error> {
234///         TranscodeFailure::ensure_output_index(output.len(), output_index)?;
235///         Ok(0)
236///     }
237/// }
238///
239/// let mut transcoder = U16BeBytesDecoder::default();
240/// let mut reset_output = [];
241/// transcoder
242///     .reset(&mut reset_output, 0)
243///     .expect("stateless decoder reset cannot fail");
244/// let mut output = [0_u16; 1];
245/// let progress = transcoder
246///     .transcode(&[0x12, 0x34, 0xab, 0xcd], 0, &mut output, 0)
247///     .expect("decoding cannot fail");
248/// assert_eq!(TranscodeStatus::NeedOutput {
249///     required: NonZeroUsize::MIN,
250/// }, progress.status());
251/// assert_eq!(2, progress.read());
252/// assert_eq!(1, progress.written());
253/// assert_eq!([0x1234], output);
254///
255/// let mut output = [0_u16; 2];
256/// let progress = transcoder
257///     .transcode(&[0x12, 0x34, 0xab], 0, &mut output, 0)
258///     .expect("decoding cannot fail");
259/// assert_eq!(TranscodeStatus::NeedInput {
260///     required: NonZeroUsize::new(2).expect("two is non-zero"),
261/// }, progress.status());
262/// assert_eq!(2, progress.read());
263/// assert_eq!(1, progress.written());
264/// assert_eq!([0x1234, 0], output);
265///
266/// assert!(matches!(
267///     transcoder.transcode(&[0x12], 2, &mut output, 0),
268///     Err(TranscodeDecodeError::Failure(
269///         TranscodeFailure::InvalidInputIndex { .. }
270///     )),
271/// ));
272/// assert!(matches!(
273///     transcoder.transcode(&[0x12], 0, &mut output, 3),
274///     Err(TranscodeDecodeError::Failure(
275///         TranscodeFailure::InvalidOutputIndex { .. }
276///     )),
277/// ));
278/// ```
279///
280/// The trait is intentionally independent from charset concepts. Implementors
281/// use `input_index` and `output_index` as absolute positions in the supplied
282/// slices. Returned progress counters are relative counts from those positions.
283/// For raw codecs this gives a compact API; higher-level workflows can wrap
284/// this trait with their own semantic policies.
285pub trait Transcoder {
286    /// Input unit type accepted by this transcoder.
287    type Input;
288
289    /// Output unit type produced by this transcoder.
290    type Output;
291
292    /// Complete error type produced by this transcoder.
293    type Error: From<TranscodeFailure>;
294
295    /// Returns an upper bound for output units emitted when resetting stream
296    /// state.
297    ///
298    /// Stateful encoders may need a stream-start sequence, such as a byte
299    /// order mark, before the first encoded value. Callers use this bound to
300    /// size the output buffer passed to [`Transcoder::reset`].
301    ///
302    /// The bound may depend on immutable transcoder configuration, but it must
303    /// cover reset output from every reachable transient stream state. It must
304    /// not shrink merely because the current stream has already been reset or
305    /// finished.
306    ///
307    /// # Returns
308    ///
309    /// Returns `Ok(bound)` when the upper bound can be represented as `usize`.
310    /// Stateless transcoders default to `Ok(0)`.
311    ///
312    /// # Errors
313    ///
314    /// Implementations return [`CapacityError::OutputLengthOverflow`] when
315    /// capacity arithmetic overflows. The default never fails.
316    #[must_use = "capacity planning can fail on overflow"]
317    #[inline]
318    fn max_reset_output_len(&self) -> Result<usize, CapacityError> {
319        Ok(0)
320    }
321
322    /// Returns an upper bound for output units produced from `input_len` units
323    /// during the streaming transcode phase.
324    ///
325    /// This bound excludes stream-start output emitted by
326    /// [`Transcoder::reset`] and final output emitted by
327    /// [`Transcoder::finish`]. Callers that need a complete one-shot stream
328    /// bound should use [`Transcoder::max_total_output_len`].
329    ///
330    /// The bound may depend on immutable transcoder configuration and
331    /// `input_len`, but it must cover the streaming output reachable from every
332    /// transient stream state. In particular, it must include the maximum
333    /// retained output that may be emitted before or alongside output derived
334    /// from the supplied input, even when no output is currently retained.
335    ///
336    /// # Parameters
337    ///
338    /// - `input_len`: Number of input units the caller plans to transcode.
339    ///
340    /// # Returns
341    ///
342    /// Returns `Ok(bound)` when the upper bound can be represented as `usize`.
343    ///
344    /// # Errors
345    ///
346    /// Returns [`CapacityError::OutputLengthOverflow`] when capacity arithmetic
347    /// overflows.
348    #[must_use = "capacity planning can fail on overflow"]
349    fn max_transcode_output_len(&self, input_len: usize) -> Result<usize, CapacityError>;
350
351    /// Returns an upper bound for a complete `reset -> transcode -> finish`
352    /// stream.
353    ///
354    /// This is a convenience sum of [`Transcoder::max_reset_output_len`],
355    /// [`Transcoder::max_transcode_output_len`], and
356    /// [`Transcoder::max_finish_output_len`]. Each component is independent of
357    /// transient stream state, so the sum is valid before a full one-shot
358    /// stream regardless of how the previous logical stream ended.
359    ///
360    /// # Parameters
361    ///
362    /// - `input_len`: Number of input units in the complete stream.
363    ///
364    /// # Returns
365    ///
366    /// Returns `Ok(bound)` when the full-stream upper bound can be represented
367    /// as `usize`.
368    ///
369    /// # Errors
370    ///
371    /// Returns [`CapacityError::OutputLengthOverflow`] when a component bound
372    /// or the sum of lifecycle bounds overflows.
373    #[must_use = "capacity planning can fail on overflow"]
374    #[inline]
375    fn max_total_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
376        let reset = self.max_reset_output_len()?;
377        let transcode = self.max_transcode_output_len(input_len)?;
378        let finish = self.max_finish_output_len()?;
379        sum_output_bounds(reset, transcode, finish)
380    }
381
382    /// Returns an upper bound for output units produced by stream finalization.
383    ///
384    /// The bound may depend on immutable transcoder configuration, but it must
385    /// cover final output from every reachable transient stream state. For
386    /// example, an encoder that can emit one checksum byte must return at least
387    /// `1` before input, while accumulating the checksum, and after finishing a
388    /// previous stream. It must not shrink merely because no finish output is
389    /// currently pending.
390    ///
391    /// # Returns
392    ///
393    /// Returns `Ok(bound)` when the upper bound can be represented as `usize`.
394    /// Stateless transcoders default to `Ok(0)`.
395    ///
396    /// # Errors
397    ///
398    /// Implementations return [`CapacityError::OutputLengthOverflow`] when
399    /// capacity arithmetic overflows. The default never fails.
400    #[must_use = "capacity planning can fail on overflow"]
401    #[inline]
402    fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
403        Ok(0)
404    }
405
406    /// Resets stream state and emits stream-start output into `output`.
407    ///
408    /// This starts a new logical stream while keeping configuration such as
409    /// byte order, charset policy, replacement values, and cryptographic keys.
410    /// Pending input, pending output, and completed-stream state must be
411    /// discarded by stateful implementations. The caller must provide enough
412    /// output capacity for [`Transcoder::max_reset_output_len`].
413    ///
414    /// # Parameters
415    ///
416    /// - `output`: Complete output unit slice visible to the transcoder.
417    /// - `output_index`: Absolute output unit index where writing starts.
418    ///
419    /// # Returns
420    ///
421    /// Returns the number of units written while resetting stream state.
422    /// Stateless transcoders return `0`. Implementations must not return a
423    /// count greater than [`Transcoder::max_reset_output_len`] or the output
424    /// units available from `output_index`.
425    ///
426    /// # Errors
427    ///
428    /// Returns contract errors (`invalid_output_index`, `insufficient_output`)
429    /// when capacity checks fail, or policy errors when reset itself fails.
430    fn reset(&mut self, output: &mut [Self::Output], output_index: usize) -> Result<usize, Self::Error>;
431
432    /// Converts available input units into output units.
433    ///
434    /// This method processes an input segment without closing the logical input
435    /// stream. When the current segment ends in a partial value, the transcoder
436    /// reports [`crate::TranscodeStatus::NeedInput`] without consuming that
437    /// tail. The caller owns input-buffer refill and EOF incomplete-tail
438    /// policy.
439    ///
440    /// Returning [`crate::TranscodeStatus::Complete`] means all input visible
441    /// from `input_index` was consumed. Implementations must not return
442    /// `Complete` after consuming only a prefix of the supplied input; they
443    /// must instead continue processing, report `NeedInput` for an
444    /// incomplete tail, or report `NeedOutput` if output capacity prevents
445    /// progress. This invariant is checked by
446    /// [`crate::TranscodeProgress::validate`].
447    ///
448    /// # Parameters
449    ///
450    /// - `input`: Complete input unit slice visible to the transcoder.
451    /// - `input_index`: Absolute input unit index where conversion starts.
452    /// - `output`: Complete output unit slice visible to the transcoder.
453    /// - `output_index`: Absolute output unit index where writing starts.
454    ///
455    /// # Returns
456    ///
457    /// Returns progress describing how many units were consumed and produced
458    /// and why conversion stopped. Implementations must keep the returned
459    /// counters and status fields consistent with the supplied input and
460    /// output ranges. For `Complete`, `read` must equal `input.len() -
461    /// input_index`. The default one-shot helper rejects incomplete `Complete`
462    /// progress and asserts the remaining progress contract in all builds;
463    /// streaming I/O drivers may convert validation failures before advancing
464    /// unsafe cursors.
465    ///
466    /// # Errors
467    ///
468    /// Returns framework failures for invalid indices, insufficient output,
469    /// incomplete input, and capacity overflow. Returns domain errors for
470    /// semantic conversion failures that the transcoder's policy does not
471    /// absorb.
472    fn transcode(
473        &mut self,
474        input: &[Self::Input],
475        input_index: usize,
476        output: &mut [Self::Output],
477        output_index: usize,
478    ) -> Result<TranscodeProgress, Self::Error>;
479
480    /// Converts source units after the caller has established end of input.
481    ///
482    /// The default preserves ordinary streaming behavior. Implementations that
483    /// can resolve a trailing prefix using EOF-aware format rules should
484    /// override it. If the default still reports
485    /// [`TranscodeStatus::NeedInput`], this method returns
486    /// [`TranscodeFailure::IncompleteInput`] instead. The default validates
487    /// its input and output indices before delegating, then validates returned
488    /// progress before calculating the incomplete-input context. A broken
489    /// implementation therefore returns [`TranscodeFailure::InvalidProgress`]
490    /// rather than causing arithmetic overflow or advancing invalid cursors.
491    ///
492    /// # Parameters
493    ///
494    /// - `input`: Complete input slice, including the final source segment.
495    /// - `input_index`: Absolute position where final decoding starts.
496    /// - `output`: Destination output slice.
497    /// - `output_index`: Absolute output position where writing starts.
498    ///
499    /// # Returns
500    ///
501    /// Validated complete or output-limited progress. The default never returns
502    /// a successful `NeedInput` status at EOF.
503    ///
504    /// # Errors
505    ///
506    /// Returns invalid-index errors before calling `transcode`, forwards its
507    /// errors, and rejects invalid progress or an incomplete final input tail.
508    #[inline]
509    fn transcode_eof(
510        &mut self,
511        input: &[Self::Input],
512        input_index: usize,
513        output: &mut [Self::Output],
514        output_index: usize,
515    ) -> Result<TranscodeProgress, Self::Error> {
516        TranscodeFailure::ensure_transcode_indices(input.len(), input_index, output.len(), output_index)?;
517        let available_input = input.len() - input_index;
518        let available_output = output.len() - output_index;
519        let progress = self.transcode(input, input_index, output, output_index)?;
520        progress
521            .validate(input_index, available_input, output_index, available_output)
522            .map_err(TranscodeFailure::invalid_progress)?;
523        match progress.status() {
524            TranscodeStatus::NeedInput { required } => Err(TranscodeFailure::incomplete_input(
525                input_index + progress.read(),
526                required.get(),
527                available_input - progress.read(),
528            )
529            .into()),
530            _ => Ok(progress),
531        }
532    }
533
534    /// Finishes internally retained output after all input has been supplied.
535    ///
536    /// `transcode` handles ordinary input consumption. `finish` is called once
537    /// after the caller knows no more input remains and has handled any
538    /// incomplete input tail reported by `transcode`. It emits final output
539    /// derived from internal state, such as reset bytes, checksums, digests, or
540    /// trailers. The caller must provide enough output capacity for
541    /// [`Transcoder::max_finish_output_len`].
542    ///
543    /// After `finish` succeeds, the logical stream is closed. Portable callers
544    /// should call [`Transcoder::reset`] with a buffer sized by
545    /// [`Transcoder::max_reset_output_len`] before passing input for another
546    /// logical stream to the same instance.
547    ///
548    /// # Examples
549    ///
550    /// ```rust
551    /// use core::convert::Infallible;
552    /// use core::num::NonZeroUsize;
553    ///
554    /// use qubit_codec::CapacityError;
555    /// use qubit_codec::TranscodeDecodeError;
556    /// use qubit_codec::TranscodeFailure;
557    /// use qubit_codec::TranscodeProgress;
558    /// use qubit_codec::TranscodeStatus;
559    /// use qubit_codec::Transcoder;
560    ///
561    /// #[derive(Default)]
562    /// struct ByteCopy;
563    ///
564    /// impl Transcoder for ByteCopy {
565    ///     type Input = u8;
566    ///     type Output = u8;
567    ///     type Error = TranscodeDecodeError<Infallible>;
568    ///
569    ///     fn max_transcode_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
570    ///         Ok(input_len)
571    ///     }
572    ///
573    ///     fn reset(
574    ///         &mut self,
575    ///         output: &mut [u8],
576    ///         output_index: usize,
577    ///     ) -> Result<usize, Self::Error> {
578    ///         TranscodeFailure::ensure_output_index(output.len(), output_index)?;
579    ///         Ok(0)
580    ///     }
581    ///
582    ///     fn transcode(
583    ///         &mut self,
584    ///         input: &[u8],
585    ///         input_index: usize,
586    ///         output: &mut [u8],
587    ///         output_index: usize,
588    ///     ) -> Result<TranscodeProgress, Self::Error> {
589    ///         let mut read = 0;
590    ///         let mut written = 0;
591    ///         while input_index + read < input.len() && output_index + written < output.len() {
592    ///             output[output_index + written] = input[input_index + read];
593    ///             read += 1;
594    ///             written += 1;
595    ///         }
596    ///         if input_index + read == input.len() {
597    ///             Ok(TranscodeProgress::complete(read, written))
598    ///         } else {
599    ///             let status = TranscodeStatus::NeedOutput {
600    ///                 required: NonZeroUsize::MIN,
601    ///             };
602    ///             Ok(TranscodeProgress::new(
603    ///                 status,
604    ///                 read,
605    ///                 written,
606    ///             ))
607    ///         }
608    ///     }
609    ///
610    ///     fn finish(
611    ///         &mut self,
612    ///         output: &mut [u8],
613    ///         output_index: usize,
614    ///     ) -> Result<usize, Self::Error> {
615    ///         TranscodeFailure::ensure_output_index(output.len(), output_index)?;
616    ///         Ok(0)
617    ///     }
618    /// }
619    ///
620    /// let mut transcoder = ByteCopy::default();
621    /// let mut reset_output = [];
622    /// transcoder
623    ///     .reset(&mut reset_output, 0)
624    ///     .expect("stateless transcoder reset cannot fail");
625    /// let mut output = [1_u8; 1];
626    /// let progress = transcoder
627    ///     .transcode(&[7], 0, &mut output, 0)
628    ///     .expect("writer consumes one unit");
629    /// assert_eq!(TranscodeStatus::Complete, progress.status());
630    ///
631    /// let written = transcoder
632    ///     .finish(&mut output, 1)
633    ///     .expect("finish does not emit final state for no-op transcoders");
634    /// assert_eq!(0, written);
635    /// ```
636    ///
637    /// # Parameters
638    ///
639    /// - `output`: Complete output unit slice visible to the transcoder.
640    /// - `output_index`: Absolute output unit index where writing starts.
641    ///
642    /// # Returns
643    ///
644    /// Returns the number of units written during finalization. Stateless
645    /// transcoders return `0`. Implementations must not return a count greater
646    /// than [`Transcoder::max_finish_output_len`] or the output units available
647    /// from `output_index`.
648    ///
649    /// # Errors
650    ///
651    /// Returns contract errors (`invalid_output_index`, `insufficient_output`)
652    /// when capacity checks fail, or policy errors when finish itself
653    /// fails.
654    fn finish(&mut self, output: &mut [Self::Output], output_index: usize) -> Result<usize, Self::Error>;
655
656    /// Runs a complete one-shot `reset -> transcode -> finish` stream.
657    ///
658    /// The `input` slice is treated as complete input at EOF, and output is
659    /// written from the beginning of `output`. Callers that need to operate on
660    /// a range inside a larger buffer should slice the input or output
661    /// before calling this method.
662    ///
663    /// Before invoking any lifecycle method, this method queries the
664    /// state-independent reset, transcode, and finish bounds and requires
665    /// `output` to provide at least the complete upper bound returned by
666    /// [`Transcoder::max_total_output_len`] for `input.len()` units.
667    ///
668    /// # Parameters
669    ///
670    /// - `input`: Complete input unit slice.
671    /// - `output`: Complete output unit slice where the stream starts at index
672    ///   `0`.
673    ///
674    /// # Returns
675    ///
676    /// Returns the number of output units written to `output`.
677    ///
678    /// # Errors
679    ///
680    /// Returns framework errors when the output buffer is too small, when
681    /// capacity arithmetic overflows, or when the complete input ends with
682    /// an incomplete value. Capacity-bound overflow and insufficient
683    /// complete-stream capacity are reported before `reset`, `transcode`, or
684    /// `finish` is called, so those preflight failures do not write to
685    /// `output` or advance transient stream state through lifecycle methods.
686    /// Once reset begins, domain errors, incomplete input, runtime
687    /// backpressure caused by an invalid bound implementation, or contract
688    /// violations may leave partial output and advanced state; this method
689    /// does not provide general transactional rollback.
690    ///
691    /// # Panics
692    ///
693    /// Panics when `reset` or `finish` reports writing beyond its declared
694    /// bound or the available output, when an overridden `transcode_eof`
695    /// returns invalid progress, or when a returned write count cannot be
696    /// added to the output cursor. The default `transcode_eof` converts
697    /// invalid `transcode` progress into [`TranscodeFailure::InvalidProgress`].
698    fn transcode_complete_into(
699        &mut self,
700        input: &[Self::Input],
701        output: &mut [Self::Output],
702    ) -> Result<usize, Self::Error> {
703        let reset_required = self.max_reset_output_len().map_err(TranscodeFailure::from)?;
704        let transcode_required = self
705            .max_transcode_output_len(input.len())
706            .map_err(TranscodeFailure::from)?;
707        let finish_required = self.max_finish_output_len().map_err(TranscodeFailure::from)?;
708        let total_required =
709            sum_output_bounds(reset_required, transcode_required, finish_required).map_err(TranscodeFailure::from)?;
710        TranscodeFailure::ensure_output_capacity(output.len(), 0, total_required)?;
711
712        let reset_written = self.reset(output, 0)?;
713        assert!(
714            reset_written <= reset_required,
715            "Transcoder::reset wrote beyond its bound",
716        );
717        assert!(
718            reset_written <= output.len(),
719            "Transcoder::reset wrote beyond available output",
720        );
721        let mut output_cursor = reset_written;
722
723        let progress = self.transcode_eof(input, 0, output, output_cursor)?;
724        let transcode_written = complete_progress_written(progress, input.len(), output_cursor, output.len())?;
725        output_cursor = output_cursor
726            .checked_add(transcode_written)
727            .expect("Transcoder::transcode write count overflowed the output cursor");
728
729        let finish_available = output.len() - output_cursor;
730        let finish_written = self.finish(output, output_cursor)?;
731        assert!(
732            finish_written <= finish_required,
733            "Transcoder::finish wrote beyond its bound",
734        );
735        assert!(
736            finish_written <= finish_available,
737            "Transcoder::finish wrote beyond available output",
738        );
739        output_cursor = output_cursor
740            .checked_add(finish_written)
741            .expect("Transcoder::finish write count overflowed the output cursor");
742        Ok(output_cursor)
743    }
744}