qubit-text-codec 0.1.0

Buffer-oriented text encoding and decoding utilities for Rust
Documentation
use qubit_text_codec::{
    Charset,
    CharsetCodec,
    CharsetDecodeErrorKind,
    CharsetEncodeErrorKind,
    DecodeStatus,
    Utf8,
    Utf8Codec,
};

#[test]
fn test_utf8_codec_exposes_encoder_and_decoder_contracts() {
    let codec = Utf8Codec;

    assert_eq!(Charset::UTF_8, <Utf8Codec as CharsetCodec>::charset(&codec));
    assert_eq!(
        Utf8::MAX_UNITS_PER_CHAR,
        <Utf8Codec as CharsetCodec>::max_units_per_char(&codec)
    );
    assert_eq!(
        DecodeStatus::NeedMore {
            required: 1,
            available: 0,
        },
        <Utf8Codec as CharsetCodec>::decode_one(&codec, &[], 0).expect("utf8 need more"),
    );
    assert_eq!(
        1,
        <Utf8Codec as CharsetCodec>::encode_one(&codec, 'A', &mut [0_u8; 4], 0).expect("encode ascii as utf8"),
    );

    assert_eq!(Charset::UTF_8, codec.charset());
    assert_eq!(Utf8::MAX_UNITS_PER_CHAR, codec.max_units_per_char());
    assert_eq!(Utf8::MAX_UNITS_PER_CHAR, codec.max_units_per_char());
}

#[test]
fn test_utf8_codec_encodes_and_decodes() {
    let codec = Utf8Codec;
    let mut output = [0_u8; Utf8::MAX_BYTES_PER_CHAR];

    assert_eq!(2, codec.encode_one('é', &mut output, 0).expect("Latin-1"));
    assert!(matches!(
        codec.decode_one(&output[..2], 0).expect("decode Latin-1"),
        DecodeStatus::Complete {
            value: 'é',
            consumed: 2,
        },
    ));
    assert!(matches!(
        codec.decode_one(&[], 0).expect("empty prefix needs more"),
        DecodeStatus::NeedMore { .. },
    ));
}

#[test]
fn test_utf8_codec_decodes_all_lengths_and_partial_prefixes() {
    let codec = Utf8Codec;

    assert_eq!(
        DecodeStatus::Complete {
            value: 'A',
            consumed: 1,
        },
        codec.decode_one(b"A", 0).expect("ASCII"),
    );
    assert_eq!(
        DecodeStatus::Complete {
            value: '',
            consumed: 3,
        },
        codec.decode_one("".as_bytes(), 0).expect("three bytes"),
    );
    assert_eq!(
        DecodeStatus::Complete {
            value: '😀',
            consumed: 4,
        },
        codec.decode_one("😀".as_bytes(), 0).expect("four bytes"),
    );
    assert_eq!(
        DecodeStatus::NeedMore {
            required: 3,
            available: 1,
        },
        codec.decode_one(&[0xe4], 0).expect("partial three-byte prefix"),
    );
    assert_eq!(
        DecodeStatus::NeedMore {
            required: 4,
            available: 2,
        },
        codec.decode_one(&[0xf0, 0x90], 0).expect("partial four-byte prefix"),
    );
}

#[test]
fn test_utf8_codec_reports_malformed_sequences() {
    let codec = Utf8Codec;

    let cases = [
        (&[0x80][..], 0, Some(0x80)),
        (&[0xc2, b' '][..], 1, Some(b' ' as u32)),
        (&[0xe0, 0x80, 0x80][..], 1, Some(0x80)),
        (&[0xed, 0xa0, 0x80][..], 1, Some(0xa0)),
        (&[0xe1, 0x80, b' '][..], 2, Some(0x20)),
        (&[0xf0, 0x80, 0x80, 0x80][..], 1, Some(0x80)),
        (&[0xf1, 0x80, b' ', 0x80][..], 2, Some(0x20)),
        (&[0xf4, 0xc0, 0x80, 0x80][..], 1, Some(0xc0)),
        (&[0xf4, 0x80, 0x80, b' '][..], 3, Some(0x20)),
        (&[0xe4, b' '][..], 1, Some(0x20)),
        (&[0xe4, 0xb8, b' '][..], 2, Some(0x20)),
        (&[0xf0, 0x90, b' '][..], 2, Some(0x20)),
    ];

    for (input, index, value) in cases {
        let error = codec.decode_one(input, 0).expect_err("malformed UTF-8 should fail");
        assert_eq!(CharsetDecodeErrorKind::MalformedSequence { value }, error.kind());
        assert_eq!(index, error.index());
    }

    let error = codec
        .decode_one(b"", 1)
        .expect_err("input index outside slice should fail");
    assert_eq!(CharsetDecodeErrorKind::MalformedSequence { value: None }, error.kind());
    assert_eq!(1, error.index());
}

#[test]
fn test_utf8_codec_encodes_all_lengths_and_reports_small_buffers() {
    let codec = Utf8Codec;
    let mut output = [0_u8; Utf8::MAX_BYTES_PER_CHAR];

    assert_eq!(1, codec.encode_one('A', &mut output, 0).expect("ASCII"));
    assert_eq!(2, codec.encode_one('é', &mut output, 0).expect("two bytes"));
    assert_eq!(3, codec.encode_one('', &mut output, 0).expect("three bytes"));
    assert_eq!(4, codec.encode_one('😀', &mut output, 0).expect("four bytes"));

    let error = codec
        .encode_one('A', &mut output[..0], 1)
        .expect_err("output index outside slice should fail");
    assert!(matches!(error.kind(), CharsetEncodeErrorKind::BufferTooSmall { .. },));
    assert_eq!(1, error.index());

    let error = codec
        .encode_one('', &mut output[..2], 0)
        .expect_err("short output should fail");
    assert!(matches!(error.kind(), CharsetEncodeErrorKind::BufferTooSmall { .. },));
    assert_eq!(0, error.index());
}