qubit-codec-misc 0.8.2

Miscellaneous byte and text format codecs for Rust applications
Documentation
// =============================================================================
//    Copyright (c) 2026 Haixing Hu.
//
//    SPDX-License-Identifier: Apache-2.0
//
//    Licensed under the Apache License, Version 2.0.
// =============================================================================
use qubit_codec::TranscodeFailure;
use qubit_codec::TranscodeStatus;
use qubit_codec::Transcoder;
use qubit_codec_misc::Base64Codec;
use qubit_codec_misc::Base64Encoder;

#[test]
fn test_base64_encoder_constructors_are_independent_of_codec_lifetime() {
    let encoder = {
        let codec = Base64Codec::url_safe_no_pad();
        codec.encoder()
    };
    let constructed = [
        Base64Encoder::standard(),
        Base64Encoder::standard_no_pad(),
        Base64Encoder::url_safe(),
        Base64Encoder::url_safe_no_pad(),
        Base64Encoder::default(),
        encoder,
    ];
    assert_eq!(constructed.len(), 6);
}

#[test]
fn test_base64_encoder_retains_two_bytes_then_reports_output_pressure() {
    let mut encoder = Base64Encoder::standard();
    encoder.reset(&mut [], 0).expect("reset");
    let progress = encoder.transcode(b"Man", 0, &mut [], 0).expect("backpressure");
    assert_eq!(progress.read(), 2);
    assert_eq!(progress.written(), 0);
    assert!(matches!(progress.status(), TranscodeStatus::NeedOutput { required } if required.get() == 4));
    let mut out = [0; 4];
    let progress = encoder.transcode(b"n", 0, &mut out, 0).expect("resume");
    assert_eq!(progress.read(), 1);
    assert_eq!(progress.written(), 4);
    assert_eq!(progress.status(), TranscodeStatus::Complete);
    assert_eq!(&out, b"TWFu");
    assert_eq!(encoder.finish(&mut [], 0).expect("finish"), 0);
}

#[test]
fn test_base64_encoder_consumes_one_byte_per_call() {
    let mut encoder = Base64Encoder::standard();
    encoder.reset(&mut [], 0).expect("reset");
    let mut out = [0; 4];
    for (input, expected_read) in [(b"M".as_slice(), 1), (b"a".as_slice(), 1)] {
        let progress = encoder
            .transcode(input, 0, &mut out, 0)
            .expect("retain partial quantum");
        assert_eq!(progress.status(), TranscodeStatus::Complete);
        assert_eq!(progress.read(), expected_read);
        assert_eq!(progress.written(), 0);
    }
    let progress = encoder.transcode(b"n", 0, &mut out, 0).expect("complete quantum");
    assert_eq!(progress.status(), TranscodeStatus::Complete);
    assert_eq!(progress.read(), 1);
    assert_eq!(progress.written(), 4);
    assert_eq!(&out, b"TWFu");
}

#[test]
fn test_base64_encoder_output_pressure_at_nonzero_output_index() {
    let mut encoder = Base64Encoder::standard();
    encoder.reset(&mut [], 0).expect("reset");
    let mut out = [0xa5; 6];
    let progress = encoder.transcode(b"Man", 0, &mut out[..5], 2).expect("backpressure");
    assert!(matches!(progress.status(), TranscodeStatus::NeedOutput { required } if required.get() == 4));
    assert_eq!(progress.read(), 2);
    assert_eq!(progress.written(), 0);
    assert_eq!(out, [0xa5; 6]);

    let progress = encoder
        .transcode(b"n", 0, &mut out, 2)
        .expect("resume at same output index");
    assert_eq!(progress.status(), TranscodeStatus::Complete);
    assert_eq!(progress.read(), 1);
    assert_eq!(progress.written(), 4);
    assert_eq!(&out[2..6], b"TWFu");
    assert_eq!(&out[..2], &[0xa5; 2]);
}

#[test]
fn test_base64_encoder_finish_capacity_failure_preserves_tail() {
    let mut encoder = Base64Encoder::standard();
    encoder.reset(&mut [], 0).expect("reset");
    let p = encoder.transcode(b"M", 0, &mut [], 0).expect("retain tail");
    assert_eq!(p.read(), 1);
    assert_eq!(p.status(), TranscodeStatus::Complete);
    let mut short = [0xa5; 3];
    let error = encoder.finish(&mut short, 0).expect_err("short output");
    assert!(matches!(
        error.failure_ref(),
        Some(TranscodeFailure::InsufficientOutput {
            required: 4,
            available: 3,
            ..
        })
    ));
    assert_eq!(short, [0xa5; 3]);
    let mut out = [0; 4];
    assert_eq!(encoder.finish(&mut out, 0).expect("retry finish"), 4);
    assert_eq!(&out, b"TQ==");
}

#[test]
fn test_base64_encoder_all_modes_and_every_input_split() {
    type Mode = (fn() -> Base64Codec, fn() -> Base64Encoder);
    let modes: [Mode; 4] = [
        (Base64Codec::standard, Base64Encoder::standard),
        (Base64Codec::standard_no_pad, Base64Encoder::standard_no_pad),
        (Base64Codec::url_safe, Base64Encoder::url_safe),
        (Base64Codec::url_safe_no_pad, Base64Encoder::url_safe_no_pad),
    ];
    let cases: &[&[u8]] = &[b"", b"M", b"Ma", b"Man", b"Mann", &[0xfb, 0xff, 0xee, 0]];
    for (make_codec, make_encoder) in modes {
        let codec = make_codec();
        for payload in cases {
            let expected = codec.encode(payload);
            for mut encoder in [make_encoder(), codec.encoder()] {
                for split in 0..=payload.len() {
                    let mut output = [0xa5; 32];
                    encoder.reset(&mut [], 0).expect("reset");
                    let first = encoder
                        .transcode(&payload[..split], 0, &mut output, 0)
                        .expect("first segment");
                    assert_eq!(first.status(), TranscodeStatus::Complete);
                    assert_eq!(first.read(), split);
                    let second = encoder
                        .transcode_eof(&payload[split..], 0, &mut output, first.written())
                        .expect("final segment");
                    assert_eq!(second.status(), TranscodeStatus::Complete);
                    assert_eq!(second.read(), payload.len() - split);
                    let before_finish = first.written() + second.written();
                    let last = encoder.finish(&mut output, before_finish).expect("finish");
                    assert_eq!(&output[..before_finish + last], expected.as_bytes());
                }
            }
        }
    }
}

#[test]
fn test_base64_encoder_lifecycle_indices_and_reset() {
    let mut encoder = Base64Encoder::standard();
    assert!(matches!(
        encoder.finish(&mut [], 0).unwrap_err().failure_ref(),
        Some(TranscodeFailure::FinishBeforeReset)
    ));
    encoder.reset(&mut [], 0).expect("reset");
    let mut output = [0xa5; 8];
    let first = encoder.transcode(b"xM", 1, &mut output, 2).expect("nonzero indices");
    assert_eq!(first.status(), TranscodeStatus::Complete);
    assert_eq!(first.read(), 1);
    assert_eq!(encoder.finish(&mut output, 2).expect("finish"), 4);
    assert_eq!(&output[2..6], b"TQ==");
    assert!(matches!(
        encoder.finish(&mut output, 0).unwrap_err().failure_ref(),
        Some(TranscodeFailure::FinishAfterFinish)
    ));
    assert!(matches!(
        encoder.transcode(b"", 0, &mut output, 0).unwrap_err().failure_ref(),
        Some(TranscodeFailure::TranscodeAfterFinish)
    ));

    encoder.reset(&mut [], 0).expect("reset after finish");
    let p = encoder.transcode(b"M", 0, &mut output, 0).expect("retain tail");
    assert_eq!(p.read(), 1);
    encoder.reset(&mut [], 0).expect("discard old tail");
    let p = encoder.transcode(b"a", 0, &mut output, 0).expect("new stream");
    assert_eq!(p.read(), 1);
    assert_eq!(encoder.finish(&mut output, 0).expect("finish new stream"), 4);
    assert_eq!(&output[..4], b"YQ==");
}