running-key 0.1.0

A running-key cipher encryption and decryption library for Rust.
Documentation
use cipher_utils::alphabet::Alphabet;
use vigenere_lib::{Vigenere, VigenereBuilder};

pub struct RunningKey {
    alphabet: Alphabet,
    key: String,
}

impl RunningKey {
    pub fn encrypt(&self, plaintext: &str) -> anyhow::Result<String> {
        if self.key.len() < plaintext.len() {
            anyhow::bail!("Error encrypting running-key cipher: Plaintext is shorter than key. If this is intentional, consider using a Vigenere cipher.");
        }
        let key_bytes = self.key.as_bytes();
        let mut index = 0;
        plaintext
            .chars()
            .map(|plain_char| {
                if !plain_char.is_alphabetic() {
                    return Ok(plain_char);
                }
                let key_char = key_bytes[index] as char;
                let plaintext_index = self.alphabet.index_of(plain_char).unwrap();
                let key_index = self.alphabet.index_of(key_char).unwrap();
                let result = self.alphabet.letter_at(plaintext_index + key_index - 1);
                index += 1;
                Ok(if plain_char.is_uppercase() {
                    result.to_ascii_uppercase()
                } else {
                    result.to_ascii_lowercase()
                })
            })
            .collect()
    }

    pub fn decrypt(&self, ciphertext: &str) -> anyhow::Result<String> {
        if self.key.len() < ciphertext.len() {
            anyhow::bail!("Error decrypting running-key cipher: Ciphertext is shorter than key. If this is intentional, consider using a Vigenere cipher.");
        }
        let key_bytes = self.key.as_bytes();
        let mut index = 0;
        ciphertext
            .chars()
            .map(|cipher_char| {
                if !cipher_char.is_alphabetic() {
                    return Ok(cipher_char);
                }
                let key_char = key_bytes[index] as char;
                let ciphertext_index = self.alphabet.index_of(cipher_char).unwrap();
                let key_index = self.alphabet.index_of(key_char).unwrap();
                let result = self.alphabet.letter_at(ciphertext_index - key_index + 1);
                index += 1;
                Ok(if cipher_char.is_uppercase() {
                    result.to_ascii_uppercase()
                } else {
                    result.to_ascii_lowercase()
                })
            })
            .collect()
    }
}

pub trait RunningKeyBuilder {
    fn alphabet<T: AsRef<str>>(self, alphabet: T) -> impl RunningKeyBuilder;
    fn key<T: AsRef<str>>(self, key: T) -> impl RunningKeyBuilder;
    fn build(self) -> anyhow::Result<Vigenere>;
}

#[derive(Debug, Default)]
struct IncompleteRunningKey {
    key: Option<String>,
    alphabet: Option<Alphabet>,
}

impl RunningKeyBuilder for anyhow::Result<IncompleteRunningKey> {
    fn key<T: AsRef<str>>(self, key: T) -> impl RunningKeyBuilder {
        if let Ok(mut running_key) = self {
            running_key.key = Some(key.as_ref().to_owned());
            Ok(running_key)
        } else {
            self
        }
    }

    fn alphabet<T: AsRef<str>>(self, alphabet: T) -> impl RunningKeyBuilder {
        if let Ok(mut running_key) = self {
            running_key.alphabet = Some(Alphabet::caseless(alphabet.as_ref())?);
            Ok(running_key)
        } else {
            self
        }
    }

    fn build(self) -> anyhow::Result<Vigenere> {
        if let Ok(running_key) = self {
            let Some(key) = running_key.key else {
                anyhow::bail!("Error building RunningKey: No key provided.");
            };

            let Some(alphabet) = running_key.alphabet else {
                anyhow::bail!("Error building RunningKey: No alphabet provided.");
            };

            Ok(Vigenere::new().alphabet(&alphabet.characters().iter().collect::<String>()).key(key).build().unwrap())
        } else {
            Err(self.unwrap_err())
        }
    }
}

impl RunningKey {
    #[allow(clippy::new_ret_no_self)]
    pub fn new() -> impl RunningKeyBuilder {
        Ok(IncompleteRunningKey::default())
    }
}