tidyid 2.1.1

A secure, high-performance, and human-friendly ID generator for Rust.
Documentation
use crate::{DIGITS, LETTERS, LETTERS_WITH_UPPERCASE, MAX_LENGTH};

const fn letter_byte_codes(alphabet: &[u8]) -> [u8; 256] {
    let mut lookup = [0; 256];
    let accept_limit = 256 - 256 % alphabet.len();
    let mut byte = 0;
    while byte < accept_limit {
        lookup[byte] = alphabet[byte % alphabet.len()];
        byte += 1;
    }
    lookup
}

const LOWERCASE_CODES: [u8; 256] = letter_byte_codes(LETTERS.as_bytes());
const UPPERCASE_CODES: [u8; 256] = letter_byte_codes(LETTERS_WITH_UPPERCASE.as_bytes());

#[inline]
pub(crate) fn fill_structured_id(
    output: &mut [u8],
    mut output_offset: usize,
    random: &[u8],
    allow_uppercase: bool,
) -> usize {
    debug_assert!(output.len() <= MAX_LENGTH);
    debug_assert!(output_offset <= output.len());
    let letter_codes = if allow_uppercase {
        &UPPERCASE_CODES
    } else {
        &LOWERCASE_CODES
    };

    for &byte in random {
        if output_offset == output.len() {
            break;
        }
        if (output_offset + 1) % 3 == 0 {
            output[output_offset] = DIGITS.as_bytes()[(byte & 7) as usize];
        } else {
            let code = letter_codes[byte as usize];
            if code == 0 {
                continue;
            }
            output[output_offset] = code;
        }
        output_offset += 1;
    }
    output_offset
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn accepted_ranges_are_exact_and_uniform() {
        assert_eq!(256 % DIGITS.len(), 0);

        for (alphabet, allow_uppercase) in [
            (LETTERS.as_bytes(), false),
            (LETTERS_WITH_UPPERCASE.as_bytes(), true),
        ] {
            let limit = 256 - 256 % alphabet.len();
            let mut counts = vec![0; alphabet.len()];
            for input in 0..=u8::MAX {
                let mut output = [0];
                let written = fill_structured_id(&mut output, 0, &[input], allow_uppercase);
                if usize::from(input) >= limit {
                    assert_eq!(written, 0);
                    continue;
                }
                assert_eq!(written, 1);
                let index = alphabet
                    .iter()
                    .position(|candidate| *candidate == output[0])
                    .unwrap();
                counts[index] += 1;
            }
            assert!(counts.iter().all(|count| *count == limit / alphabet.len()));
        }

        let mut counts = [0; 8];
        for input in 0..=u8::MAX {
            let mut output = [0; 3];
            assert_eq!(fill_structured_id(&mut output, 2, &[input], false), 3);
            counts[(input & 7) as usize] += 1;
        }
        assert!(counts.iter().all(|count| *count == 32));
    }

    #[test]
    fn rejection_sampling_continues_with_the_next_block() {
        let mut output = [0; 3];
        let written = fill_structured_id(&mut output, 0, &[255; 6], false);
        assert_eq!(written, 0);
        let written = fill_structured_id(&mut output, written, &[0; 6], false);
        assert_eq!(written, output.len());
        assert_eq!(&output, b"aa2");
    }

    #[test]
    fn uppercase_sampling_preserves_the_lld_structure() {
        let mut output = [0; 3];
        assert_eq!(fill_structured_id(&mut output, 0, &[0, 22, 0], true), 3);
        assert_eq!(&output, b"Aa2");
    }
}