qubit-codec 0.4.0

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

#[derive(Default)]
struct CopyTranscoder;

impl Transcoder<u8, u8> for CopyTranscoder {
    type Error = core::convert::Infallible;

    fn max_output_len(&self, input_len: usize) -> Option<usize> {
        Some(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,
                required: 1,
                available: output.len().saturating_sub(output_index + written),
            };
            Ok(TranscodeProgress::new(status, read, written))
        }
    }
}

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

impl Transcoder<u8, u8> for FinishingTranscoder {
    type Error = core::convert::Infallible;

    fn max_output_len(&self, input_len: usize) -> Option<usize> {
        Some(input_len)
    }

    fn max_finish_output_len(&self) -> Option<usize> {
        Some(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> {
        CopyTranscoder.transcode(input, input_index, output, output_index)
    }

    fn finish(&mut self, output: &mut [u8], output_index: usize) -> Result<TranscodeProgress, Self::Error> {
        let suffix = *b"!\n";
        let mut written = 0;
        while self.suffix_index < suffix.len() {
            if output_index + written == output.len() {
                let status = TranscodeStatus::NeedOutput {
                    output_index: output_index + written,
                    required: suffix.len() - self.suffix_index,
                    available: 0,
                };
                return Ok(TranscodeProgress::new(status, 0, written));
            }
            output[output_index + written] = suffix[self.suffix_index];
            self.suffix_index += 1;
            written += 1;
        }
        Ok(TranscodeProgress::complete(0, written))
    }
}

#[test]
fn test_transcoder_contract_uses_absolute_indices_and_relative_progress() {
    let mut transcoder = CopyTranscoder;
    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_transcoder_default_reset_and_finish_are_noops() {
    let mut transcoder = CopyTranscoder;
    let mut output = [0_u8; 1];

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

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

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

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

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

    let progress = transcoder
        .finish(&mut output, 0)
        .expect("finish writes suffix until output fills");

    assert!(matches!(progress.status(), TranscodeStatus::NeedOutput { .. }));
    assert_eq!(0, progress.read());
    assert_eq!(1, progress.written());
    assert_eq!([b'!'], output);
    assert_eq!(Some(1), transcoder.max_finish_output_len());

    let progress = transcoder
        .finish(&mut output, 0)
        .expect("second finish call completes suffix");

    assert_eq!(TranscodeStatus::Complete, progress.status());
    assert_eq!(0, progress.read());
    assert_eq!(1, progress.written());
    assert_eq!([b'\n'], output);
    assert_eq!(Some(0), transcoder.max_finish_output_len());

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