qubit-codec 0.5.0

Core codec traits and buffer conversion primitives for Rust
Documentation
use qubit_codec::{
    BufferedTranscoder,
    CapacityError,
    FinishError,
    TranscodeProgress,
    TranscodeStatus,
};

#[derive(Default)]
struct CopyBufferedTranscoder;

impl BufferedTranscoder<u8, u8> for CopyBufferedTranscoder {
    type Error = core::convert::Infallible;

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

    fn transcode(
        &mut self,
        input: &[u8],
        input_index: usize,
        output: &mut [u8],
        output_index: usize,
    ) -> Result<TranscodeProgress, Self::Error> {
        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 {
                output_index: output_index + written,
                additional: super::nz(1),
                available: output.len().saturating_sub(output_index + written),
            };
            Ok(TranscodeProgress::new(status, read, written))
        }
    }
}

#[derive(Default)]
struct FinishingBufferedTranscoder {
    suffix_index: usize,
}

impl BufferedTranscoder<u8, u8> for FinishingBufferedTranscoder {
    type Error = core::convert::Infallible;

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

    fn max_finish_output_len(&self) -> Result<usize, CapacityError> {
        Ok(2 - self.suffix_index)
    }

    fn reset(&mut self) {
        self.suffix_index = 0;
    }

    fn transcode(
        &mut self,
        input: &[u8],
        input_index: usize,
        output: &mut [u8],
        output_index: usize,
    ) -> Result<TranscodeProgress, Self::Error> {
        CopyBufferedTranscoder.transcode(input, input_index, output, output_index)
    }

    fn finish(&mut self, output: &mut [u8], output_index: usize) -> Result<usize, FinishError<Self::Error>> {
        let suffix = *b"!\n";
        let required = suffix.len() - self.suffix_index;
        FinishError::ensure_output_capacity(output.len(), output_index, required)?;
        let mut written = 0;
        while self.suffix_index < suffix.len() {
            output[output_index + written] = suffix[self.suffix_index];
            self.suffix_index += 1;
            written += 1;
        }
        Ok(written)
    }
}

#[test]
fn test_buffered_transcoder_contract_uses_absolute_indices_and_relative_progress() {
    let mut transcoder = CopyBufferedTranscoder;
    let mut output = [0_u8; 4];

    let progress = transcoder
        .transcode(b"abc", 1, &mut output, 2)
        .expect("infallible copy");

    assert_eq!(TranscodeStatus::Complete, progress.status());
    assert_eq!(2, progress.read());
    assert_eq!(2, progress.written());
    assert_eq!([0, 0, b'b', b'c'], output);
}

#[test]
fn test_buffered_transcoder_default_reset_and_finish_are_noops() {
    let mut transcoder = CopyBufferedTranscoder;
    let mut output = [0_u8; 1];

    assert_eq!(Ok(3), transcoder.max_output_len(3));
    assert_eq!(Ok(0), transcoder.max_finish_output_len());

    BufferedTranscoder::<u8, u8>::reset(&mut transcoder);
    let written = transcoder.finish(&mut output, 0).expect("finish is noop");

    assert_eq!(0, written);
    assert_eq!([0], output);
}

#[test]
fn test_buffered_transcoder_default_finish_reports_output_index_beyond_buffer() {
    let mut transcoder = CopyBufferedTranscoder;
    let mut output = [];

    let error = transcoder
        .finish(&mut output, 1)
        .expect_err("out-of-range finish output index should be rejected");

    assert_eq!(FinishError::InvalidOutputIndex { index: 1, len: 0 }, error);
}

#[test]
fn test_buffered_transcoder_finish_requires_one_shot_output_capacity() {
    let mut transcoder = FinishingBufferedTranscoder::default();
    let mut output = [0_u8; 1];

    assert_eq!(Ok(2), transcoder.max_finish_output_len());

    let error = transcoder
        .finish(&mut output, 0)
        .expect_err("finish should reject partial output capacity");

    assert_eq!(
        FinishError::InsufficientOutput {
            output_index: 0,
            required: 2,
            available: 1,
        },
        error,
    );
    assert_eq!([0], output);
    assert_eq!(Ok(2), transcoder.max_finish_output_len());

    let mut output = [0_u8; 2];
    let written = transcoder
        .finish(&mut output, 0)
        .expect("finish should write the whole suffix once capacity is available");

    assert_eq!(2, written);
    assert_eq!(*b"!\n", output);
    assert_eq!(Ok(0), transcoder.max_finish_output_len());

    transcoder.reset();
    assert_eq!(Ok(2), transcoder.max_finish_output_len());
}