enigma-cipher 0.1.0

An absurdly fast and highly flexible Enigma machine simulation, encryption, and decryption library.
Documentation
pub const ALPHABET: Alphabet = Alphabet {
    alphabet: "ABCDEFGHIJKLMNOPQRSTUVWXYZ".as_bytes(),
};

pub struct Alphabet<'letters> {
    alphabet: &'letters [u8],
}

impl<'a> Alphabet<'a> {
    pub fn new(alphabet: &'a str) -> anyhow::Result<Self> {
        // Check for duplicates
        let mut chars = alphabet.chars().collect::<Vec<_>>();
        chars.dedup();
        if chars.len() != alphabet.len() {
            anyhow::bail!("Duplicate letter in alphabet: {alphabet}");
        }

        if alphabet.len() != 26 {
            anyhow::bail!("Invalid alphabet length: {alphabet}");
        }

        if alphabet.chars().any(|letter| !letter.is_alphabetic()) {
            anyhow::bail!("Invalid character found in alphabet: {alphabet}");
        }

        Ok(Self { alphabet: alphabet.as_bytes() })
    }

    pub fn index_of(&self, letter: char) -> Option<AlphabetIndex> {
        let letter = letter.to_ascii_uppercase();
        let code = letter as u8;
        letter.is_ascii_uppercase().then(|| {
            for (index, character) in self.alphabet.iter().enumerate() {
                if character == &code {
                    return AlphabetIndex { value: index as u8 };
                }
            }

            unreachable!()
        })
    }

    pub fn letter_at<T: std::borrow::Borrow<AlphabetIndex>>(&self, index: T) -> char {
        self.alphabet[**index.borrow() as usize] as char
    }

    pub fn letters(&self) -> String {
        self.alphabet.iter().map(|letter| *letter as char).collect()
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AlphabetIndex {
    value: u8,
}

impl std::ops::Deref for AlphabetIndex {
    type Target = u8;

    fn deref(&self) -> &Self::Target {
        &self.value
    }
}

pub trait TryIntoAlphabetIndex {
    type Output;

    fn try_into_alphabet_index(self) -> anyhow::Result<Self::Output>;
}

impl TryFrom<u8> for AlphabetIndex {
    type Error = anyhow::Error;

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        (0..26)
            .contains(&value)
            .then_some(Self { value })
            .ok_or_else(|| anyhow::anyhow!("Alphabet index out of range: {value}"))
    }
}

impl TryFrom<i32> for AlphabetIndex {
    type Error = anyhow::Error;

    fn try_from(value: i32) -> Result<Self, Self::Error> {
        (0..26)
            .contains(&value)
            .then_some(Self { value: value as u8 })
            .ok_or_else(|| anyhow::anyhow!("Alphabet index out of range: {value}"))
    }
}

impl TryIntoAlphabetIndex for (u8, u8, u8) {
    type Output = (AlphabetIndex, AlphabetIndex, AlphabetIndex);

    fn try_into_alphabet_index(self) -> anyhow::Result<Self::Output> {
        Ok((self.0.try_into()?, self.1.try_into()?, self.2.try_into()?))
    }
}

impl TryIntoAlphabetIndex for (i32, i32, i32) {
    type Output = (AlphabetIndex, AlphabetIndex, AlphabetIndex);

    fn try_into_alphabet_index(self) -> anyhow::Result<Self::Output> {
        Ok((self.0.try_into()?, self.1.try_into()?, self.2.try_into()?))
    }
}

impl std::ops::AddAssign<i32> for AlphabetIndex {
    fn add_assign(&mut self, rhs: i32) {
        *self = AlphabetIndex {
            value: (self.value + rhs as u8) % 26,
        }
    }
}

impl std::ops::Add<AlphabetIndex> for AlphabetIndex {
    type Output = AlphabetIndex;

    fn add(self, rhs: AlphabetIndex) -> Self::Output {
        AlphabetIndex {
            value: (self.value + rhs.value) % 26,
        }
    }
}

impl std::ops::Sub<AlphabetIndex> for AlphabetIndex {
    type Output = AlphabetIndex;

    fn sub(self, rhs: AlphabetIndex) -> Self::Output {
        AlphabetIndex {
            value: ((self.value as i32 - rhs.value as i32 + 26) % 26) as u8,
        }
    }
}