Skip to main content

Transcoder

Trait Transcoder 

Source
pub trait Transcoder {
    type Input;
    type Output;
    type Error: From<TranscodeFailure>;

    // Required methods
    fn max_transcode_output_len(
        &self,
        input_len: usize,
    ) -> Result<usize, CapacityError>;
    fn reset(
        &mut self,
        output: &mut [Self::Output],
        output_index: usize,
    ) -> Result<usize, Self::Error>;
    fn transcode(
        &mut self,
        input: &[Self::Input],
        input_index: usize,
        output: &mut [Self::Output],
        output_index: usize,
    ) -> Result<TranscodeProgress, Self::Error>;
    fn finish(
        &mut self,
        output: &mut [Self::Output],
        output_index: usize,
    ) -> Result<usize, Self::Error>;

    // Provided methods
    fn max_reset_output_len(&self) -> Result<usize, CapacityError> { ... }
    fn max_total_output_len(
        &self,
        input_len: usize,
    ) -> Result<usize, CapacityError> { ... }
    fn max_finish_output_len(&self) -> Result<usize, CapacityError> { ... }
    fn transcode_eof(
        &mut self,
        input: &[Self::Input],
        input_index: usize,
        output: &mut [Self::Output],
        output_index: usize,
    ) -> Result<TranscodeProgress, Self::Error> { ... }
    fn transcode_complete_into(
        &mut self,
        input: &[Self::Input],
        output: &mut [Self::Output],
    ) -> Result<usize, Self::Error> { ... }
}
Expand description

Converts one logical stream of input units into one logical stream of output units.

transcode is the main streaming API. It transforms a provided input segment and writes as much output as available buffer space allows. A successful transcode call must stop with exactly one of three meanings: all visible input was consumed (Complete), an incomplete tail remains caller-owned (NeedInput), or output capacity prevented further progress (NeedOutput).

A transcoder instance has a simple lifecycle:

  1. A newly created instance is uninitialized; call Transcoder::reset before the first stream. A reset instance is ready for a new logical stream.
  2. Call Transcoder::transcode zero or more times while input is available.
  3. Preserve any tail reported by crate::TranscodeStatus::NeedInput in the caller-owned input buffer.
  4. Call Transcoder::finish after the caller knows no more input remains and has handled any incomplete tail. Size this final output with Transcoder::max_finish_output_len.
  5. After Transcoder::finish succeeds, call Transcoder::reset with a buffer sized by Transcoder::max_reset_output_len before starting another logical stream with the same instance.

The method is suitable for:

  • pull-style consumers that call conversion repeatedly as buffers arrive;
  • bounded output sinks that use NeedOutput progress during transcode;
  • stateless and stateful codecs that all return progress-oriented stopping reasons.

finish finalizes retained state only; it does not receive source input and does not reinterpret a tail previously reported by NeedInput. For codec-backed decoders, streaming uses Codec::decode and an explicit EOF call may use Codec::decode_eof through Transcoder::transcode_eof. If a format needs EOF-aware maximal-munch parsing or must delay whether a prefix is complete until the next chunk or EOF, implement that policy in the codec or override transcode_eof.

Transcoder is intentionally independent from any charset semantics:

  • Use Transcoder directly for custom, policy-free unit transforms.
  • Use Transcoder when you want to own malformed/unmappable decisions at the call site.

§Examples

Streaming byte-to-word decoding:

use core::convert::Infallible;
use core::num::NonZeroUsize;

use qubit_codec::CapacityError;
use qubit_codec::TranscodeDecodeError;
use qubit_codec::TranscodeFailure;
use qubit_codec::TranscodeProgress;
use qubit_codec::TranscodeStatus;
use qubit_codec::Transcoder;

#[derive(Default)]
struct U16BeBytesDecoder;

impl Transcoder for U16BeBytesDecoder {
    type Input = u8;
    type Output = u16;
    type Error = TranscodeDecodeError<Infallible>;

    fn max_transcode_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
        Ok(input_len / 2)
    }

    fn reset(
        &mut self,
        output: &mut [u16],
        output_index: usize,
    ) -> Result<usize, Self::Error> {
        TranscodeFailure::ensure_output_index(output.len(), output_index)?;
        Ok(0)
    }

    fn transcode(
        &mut self,
        input: &[u8],
        input_index: usize,
        output: &mut [u16],
        output_index: usize,
    ) -> Result<TranscodeProgress, Self::Error> {
        TranscodeFailure::ensure_transcode_indices(
            input.len(),
            input_index,
            output.len(),
            output_index,
        )?;

        let mut read = 0;
        let mut written = 0;
        while input_index + read + 1 < input.len() {
            if output_index + written == output.len() {
                let status = TranscodeStatus::NeedOutput {
                    required: NonZeroUsize::MIN,
                };
                return Ok(TranscodeProgress::new(status, read, written));
            }
            let high = input[input_index + read] as u16;
            let low = input[input_index + read + 1] as u16;
            output[output_index + written] = (high << 8) | low;
            read += 2;
            written += 1;
        }
        if input_index + read == input.len() {
            Ok(TranscodeProgress::complete(read, written))
        } else {
            let status = TranscodeStatus::NeedInput {
                required: NonZeroUsize::new(2).expect("two is non-zero"),
            };
            Ok(TranscodeProgress::new(status, read, written))
        }
    }

    fn finish(
        &mut self,
        output: &mut [u16],
        output_index: usize,
    ) -> Result<usize, Self::Error> {
        TranscodeFailure::ensure_output_index(output.len(), output_index)?;
        Ok(0)
    }
}

let mut transcoder = U16BeBytesDecoder::default();
let mut reset_output = [];
transcoder
    .reset(&mut reset_output, 0)
    .expect("stateless decoder reset cannot fail");
let mut output = [0_u16; 1];
let progress = transcoder
    .transcode(&[0x12, 0x34, 0xab, 0xcd], 0, &mut output, 0)
    .expect("decoding cannot fail");
assert_eq!(TranscodeStatus::NeedOutput {
    required: NonZeroUsize::MIN,
}, progress.status());
assert_eq!(2, progress.read());
assert_eq!(1, progress.written());
assert_eq!([0x1234], output);

let mut output = [0_u16; 2];
let progress = transcoder
    .transcode(&[0x12, 0x34, 0xab], 0, &mut output, 0)
    .expect("decoding cannot fail");
assert_eq!(TranscodeStatus::NeedInput {
    required: NonZeroUsize::new(2).expect("two is non-zero"),
}, progress.status());
assert_eq!(2, progress.read());
assert_eq!(1, progress.written());
assert_eq!([0x1234, 0], output);

assert!(matches!(
    transcoder.transcode(&[0x12], 2, &mut output, 0),
    Err(TranscodeDecodeError::Failure(
        TranscodeFailure::InvalidInputIndex { .. }
    )),
));
assert!(matches!(
    transcoder.transcode(&[0x12], 0, &mut output, 3),
    Err(TranscodeDecodeError::Failure(
        TranscodeFailure::InvalidOutputIndex { .. }
    )),
));

The trait is intentionally independent from charset concepts. Implementors use input_index and output_index as absolute positions in the supplied slices. Returned progress counters are relative counts from those positions. For raw codecs this gives a compact API; higher-level workflows can wrap this trait with their own semantic policies.

Required Associated Types§

Source

type Input

Input unit type accepted by this transcoder.

Source

type Output

Output unit type produced by this transcoder.

Source

type Error: From<TranscodeFailure>

Complete error type produced by this transcoder.

Required Methods§

Source

fn max_transcode_output_len( &self, input_len: usize, ) -> Result<usize, CapacityError>

Returns an upper bound for the output capacity needed to transcode input_len units during the streaming phase.

This bound excludes stream-start output emitted by Transcoder::reset and final output emitted by Transcoder::finish. Callers that need a complete one-shot stream bound should use Transcoder::max_total_output_len.

The bound covers both Transcoder::transcode and Transcoder::transcode_eof, including any conservative capacity preflight required by those operations. It must provide enough writable capacity for the implementation to complete its preflight, even when the actual output would use fewer units.

The bound may depend on immutable transcoder configuration and input_len, but it must cover the streaming output reachable from every transient stream state. In particular, it must include the maximum retained output that may be emitted before or alongside output derived from the supplied input, even when no output is currently retained.

§Parameters
  • input_len: Number of input units the caller plans to transcode.
§Returns

Returns Ok(bound) when the upper bound can be represented as usize.

§Errors

Returns CapacityError::OutputLengthOverflow when capacity arithmetic overflows.

Source

fn reset( &mut self, output: &mut [Self::Output], output_index: usize, ) -> Result<usize, Self::Error>

Resets stream state and emits stream-start output into output.

This starts a new logical stream while keeping configuration such as byte order, charset policy, replacement values, and cryptographic keys. Pending input, pending output, and completed-stream state must be discarded by stateful implementations. The caller must provide enough output capacity for Transcoder::max_reset_output_len.

§Parameters
  • output: Complete output unit slice visible to the transcoder.
  • output_index: Absolute output unit index where writing starts.
§Returns

Returns the number of units written while resetting stream state. Stateless transcoders return 0. Implementations must not return a count greater than Transcoder::max_reset_output_len or the output units available from output_index.

§Errors

Returns contract errors (invalid_output_index, insufficient_output) when capacity checks fail, or policy errors when reset itself fails.

Source

fn transcode( &mut self, input: &[Self::Input], input_index: usize, output: &mut [Self::Output], output_index: usize, ) -> Result<TranscodeProgress, Self::Error>

Converts available input units into output units.

This method processes an input segment without closing the logical input stream. When the current segment ends in a partial value, the transcoder reports crate::TranscodeStatus::NeedInput without consuming that tail. The caller owns input-buffer refill and EOF incomplete-tail policy.

Returning crate::TranscodeStatus::Complete means all input visible from input_index was consumed. Implementations must not return Complete after consuming only a prefix of the supplied input; they must instead continue processing, report NeedInput for an incomplete tail, or report NeedOutput if output capacity prevents progress. This invariant is checked by crate::TranscodeProgress::validate.

§Parameters
  • input: Complete input unit slice visible to the transcoder.
  • input_index: Absolute input unit index where conversion starts.
  • output: Complete output unit slice visible to the transcoder.
  • output_index: Absolute output unit index where writing starts.
§Returns

Returns progress describing how many units were consumed and produced and why conversion stopped. Implementations must keep the returned counters and status fields consistent with the supplied input and output ranges. For Complete, read must equal input.len() - input_index. The default one-shot helper rejects incomplete Complete progress and asserts the remaining progress contract in all builds; streaming I/O drivers may convert validation failures before advancing unsafe cursors.

§Errors

Returns framework failures for invalid indices, insufficient output, incomplete input, and capacity overflow. Returns domain errors for semantic conversion failures that the transcoder’s policy does not absorb. An error does not provide general transactional rollback or complete progress: the transcoder may already have advanced. Callers must not replay the original input range unless the concrete transcoder provides an explicit recovery protocol. Reset may begin a new logical stream but cannot undo output already delivered.

Source

fn finish( &mut self, output: &mut [Self::Output], output_index: usize, ) -> Result<usize, Self::Error>

Finishes internally retained output after all input has been supplied.

transcode handles ordinary input consumption. finish is called once after the caller knows no more input remains and has handled any incomplete input tail reported by transcode. It emits final output derived from internal state, such as reset bytes, checksums, digests, or trailers. The caller must provide enough output capacity for Transcoder::max_finish_output_len.

After finish succeeds, the logical stream is closed. Portable callers should call Transcoder::reset with a buffer sized by Transcoder::max_reset_output_len before passing input for another logical stream to the same instance.

§Examples
use core::convert::Infallible;
use core::num::NonZeroUsize;

use qubit_codec::CapacityError;
use qubit_codec::TranscodeDecodeError;
use qubit_codec::TranscodeFailure;
use qubit_codec::TranscodeProgress;
use qubit_codec::TranscodeStatus;
use qubit_codec::Transcoder;

#[derive(Default)]
struct ByteCopy;

impl Transcoder for ByteCopy {
    type Input = u8;
    type Output = u8;
    type Error = TranscodeDecodeError<Infallible>;

    fn max_transcode_output_len(&self, input_len: usize) -> Result<usize, CapacityError> {
        Ok(input_len)
    }

    fn reset(
        &mut self,
        output: &mut [u8],
        output_index: usize,
    ) -> Result<usize, Self::Error> {
        TranscodeFailure::ensure_output_index(output.len(), output_index)?;
        Ok(0)
    }

    fn transcode(
        &mut self,
        input: &[u8],
        input_index: usize,
        output: &mut [u8],
        output_index: usize,
    ) -> Result<TranscodeProgress, Self::Error> {
        TranscodeFailure::ensure_transcode_indices(
            input.len(),
            input_index,
            output.len(),
            output_index,
        )?;
        let mut read = 0;
        let mut written = 0;
        while input_index + read < input.len() && output_index + written < output.len() {
            output[output_index + written] = input[input_index + read];
            read += 1;
            written += 1;
        }
        if input_index + read == input.len() {
            Ok(TranscodeProgress::complete(read, written))
        } else {
            let status = TranscodeStatus::NeedOutput {
                required: NonZeroUsize::MIN,
            };
            Ok(TranscodeProgress::new(
                status,
                read,
                written,
            ))
        }
    }

    fn finish(
        &mut self,
        output: &mut [u8],
        output_index: usize,
    ) -> Result<usize, Self::Error> {
        TranscodeFailure::ensure_output_index(output.len(), output_index)?;
        Ok(0)
    }
}

let mut transcoder = ByteCopy::default();
let mut reset_output = [];
transcoder
    .reset(&mut reset_output, 0)
    .expect("stateless transcoder reset cannot fail");
let mut output = [1_u8; 1];
let progress = transcoder
    .transcode(&[7], 0, &mut output, 0)
    .expect("writer consumes one unit");
assert_eq!(TranscodeStatus::Complete, progress.status());

let error = transcoder.transcode(&[7], 2, &mut output, 0).unwrap_err();
assert!(matches!(
    error.failure_ref(),
    Some(TranscodeFailure::InvalidInputIndex { index: 2, input_len: 1 })
));
let error = transcoder.transcode(&[7], 0, &mut output, 2).unwrap_err();
assert!(matches!(
    error.failure_ref(),
    Some(TranscodeFailure::InvalidOutputIndex { index: 2, output_len: 1 })
));
assert_eq!([7], output);

let written = transcoder
    .finish(&mut output, 1)
    .expect("finish does not emit final state for no-op transcoders");
assert_eq!(0, written);
§Parameters
  • output: Complete output unit slice visible to the transcoder.
  • output_index: Absolute output unit index where writing starts.
§Returns

Returns the number of units written during finalization. Stateless transcoders return 0. Implementations must not return a count greater than Transcoder::max_finish_output_len or the output units available from output_index.

§Errors

Returns contract errors (invalid_output_index, insufficient_output) when capacity checks fail, or policy errors when finish itself fails.

Provided Methods§

Source

fn max_reset_output_len(&self) -> Result<usize, CapacityError>

Returns an upper bound for output units emitted when resetting stream state.

Stateful encoders may need a stream-start sequence, such as a byte order mark, before the first encoded value. Callers use this bound to size the output buffer passed to Transcoder::reset.

The bound may depend on immutable transcoder configuration, but it must cover reset output from every reachable transient stream state. It must not shrink merely because the current stream has already been reset or finished.

§Returns

Returns Ok(bound) when the upper bound can be represented as usize. Stateless transcoders default to Ok(0).

§Errors

Implementations return CapacityError::OutputLengthOverflow when capacity arithmetic overflows. The default never fails.

Source

fn max_total_output_len(&self, input_len: usize) -> Result<usize, CapacityError>

Returns an upper bound for a complete reset -> transcode_eof -> finish stream.

This is the one-shot bound for Transcoder::transcode_complete_into and a convenience sum of Transcoder::max_reset_output_len, Transcoder::max_transcode_output_len, and Transcoder::max_finish_output_len. Each component is independent of transient stream state, so the sum is valid before a full one-shot stream regardless of how the previous logical stream ended.

§Parameters
  • input_len: Number of input units in the complete stream.
§Returns

Returns Ok(bound) when the full-stream upper bound can be represented as usize.

§Errors

Returns CapacityError::OutputLengthOverflow when a component bound or the sum of lifecycle bounds overflows.

Source

fn max_finish_output_len(&self) -> Result<usize, CapacityError>

Returns an upper bound for output units produced by stream finalization.

The bound may depend on immutable transcoder configuration, but it must cover final output from every reachable transient stream state. For example, an encoder that can emit one checksum byte must return at least 1 before input, while accumulating the checksum, and after finishing a previous stream. It must not shrink merely because no finish output is currently pending.

§Returns

Returns Ok(bound) when the upper bound can be represented as usize. Stateless transcoders default to Ok(0).

§Errors

Implementations return CapacityError::OutputLengthOverflow when capacity arithmetic overflows. The default never fails.

Source

fn transcode_eof( &mut self, input: &[Self::Input], input_index: usize, output: &mut [Self::Output], output_index: usize, ) -> Result<TranscodeProgress, Self::Error>

Converts source units after the caller has established end of input.

The default preserves ordinary streaming behavior. Implementations that can resolve a trailing prefix using EOF-aware format rules should override it. If the default still reports TranscodeStatus::NeedInput, this method returns TranscodeFailure::IncompleteInput instead. The default validates its input and output indices before delegating, then validates returned progress before calculating the incomplete-input context. A broken implementation therefore returns TranscodeFailure::InvalidProgress rather than causing arithmetic overflow or advancing invalid cursors.

§Parameters
  • input: Complete input slice, including the final source segment.
  • input_index: Absolute position where final decoding starts.
  • output: Destination output slice.
  • output_index: Absolute output position where writing starts.
§Returns

Validated complete or output-limited progress. EOF results should be Complete, NeedOutput, or an error; the default never returns a successful NeedInput status at EOF.

§Errors

Returns invalid-index errors before calling transcode, forwards its errors, and rejects invalid progress or an incomplete final input tail. It has the same non-transactional error boundary as Self::transcode: the transcoder may have advanced, and callers must not replay the original input range without an explicit recovery protocol. Reset only begins a new logical stream; it cannot undo output already delivered.

Source

fn transcode_complete_into( &mut self, input: &[Self::Input], output: &mut [Self::Output], ) -> Result<usize, Self::Error>

Runs a complete one-shot reset -> transcode -> finish stream.

The input slice is treated as complete input at EOF, and output is written from the beginning of output. Callers that need to operate on a range inside a larger buffer should slice the input or output before calling this method.

Before invoking any lifecycle method, this method queries the state-independent reset, transcode, and finish bounds and requires output to provide at least the complete upper bound returned by Transcoder::max_total_output_len for input.len() units.

§Parameters
  • input: Complete input unit slice.
  • output: Complete output unit slice where the stream starts at index 0.
§Returns

Returns the number of output units written to output.

§Errors

Returns framework errors when the output buffer is too small, when capacity arithmetic overflows, or when the complete input ends with an incomplete value. Capacity-bound overflow and insufficient complete-stream capacity are reported before reset, transcode, or finish is called, so those preflight failures do not write to output or advance transient stream state through lifecycle methods. Once reset begins, domain errors, incomplete input, runtime backpressure caused by an invalid bound implementation, or contract violations may leave partial output and advanced state; this method does not provide general transactional rollback.

§Panics

Panics when reset or finish reports writing beyond its declared bound or the available output, when an overridden transcode_eof returns invalid progress, or when a returned write count cannot be added to the output cursor. The default transcode_eof converts invalid transcode progress into TranscodeFailure::InvalidProgress.

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§

Source§

impl<C, H> Transcoder for TranscodeDecodeEngine<C, H>
where C: Codec, H: TranscodeDecodeHooks<C>,

Source§

impl<C, H> Transcoder for TranscodeEncodeEngine<C, H>
where C: Codec, H: TranscodeEncodeHooks<C>,

Source§

impl<C> Transcoder for CodecTranscodeDecoder<C>
where C: Codec,

Source§

impl<C> Transcoder for CodecTranscodeEncoder<C>
where C: Codec,

Source§

impl<D, E, DH, EH> Transcoder for TranscodeConvertEngine<D, E, DH, EH>
where D: Codec, E: Codec<Value = D::Value>, D::Value: Default, DH: TranscodeDecodeHooks<D>, EH: TranscodeEncodeHooks<E>,

Source§

impl<D, E> Transcoder for CodecTranscodeConverter<D, E>
where D: Codec, E: Codec<Value = D::Value>, D::Value: Default,