vigenere_lib/
lib.rs

1use cipher_utils::alphabet::Alphabet;
2
3pub struct Vigenere {
4    alphabet: Alphabet,
5    key: String,
6}
7
8impl Vigenere {
9    pub fn encrypt(&self, plaintext: &str) -> String {
10        let repeated_key = self.key.repeat(plaintext.len() / self.key.len());
11        let key_bytes = repeated_key.as_bytes();
12        let mut index = 0;
13        plaintext
14            .chars()
15            .map(|plain_char| {
16                if !plain_char.is_alphabetic() {
17                    return plain_char;
18                }
19                let key_char = key_bytes[index] as char;
20                let plaintext_index = self.alphabet.index_of(plain_char).unwrap();
21                let key_index = self.alphabet.index_of(key_char).unwrap();
22                let result = self.alphabet.letter_at(plaintext_index + key_index - 1);
23                index += 1;
24                if plain_char.is_uppercase() {
25                    result.to_ascii_uppercase()
26                } else {
27                    result.to_ascii_lowercase()
28                }
29            })
30            .collect()
31    }
32
33    pub fn decrypt(&self, ciphertext: &str) -> String {
34        let repeated_key = self.key.repeat(ciphertext.len() / self.key.len());
35        let key_bytes = repeated_key.as_bytes();
36        let mut index = 0;
37        ciphertext
38            .chars()
39            .map(|cipher_char| {
40                if !cipher_char.is_alphabetic() {
41                    return cipher_char;
42                }
43                let key_char = key_bytes[index] as char;
44                let ciphertext_index = self.alphabet.index_of(cipher_char).unwrap();
45                let key_index = self.alphabet.index_of(key_char).unwrap();
46                let result = self.alphabet.letter_at(ciphertext_index - key_index + 1);
47                index += 1;
48                if cipher_char.is_uppercase() {
49                    result.to_ascii_uppercase()
50                } else {
51                    result.to_ascii_lowercase()
52                }
53            })
54            .collect()
55    }
56}
57
58pub trait VigenereBuilder {
59    fn alphabet<T: AsRef<str>>(self, alphabet: T) -> impl VigenereBuilder;
60    fn key<T: AsRef<str>>(self, key: T) -> impl VigenereBuilder;
61    fn build(self) -> anyhow::Result<Vigenere>;
62}
63
64#[derive(Debug, Default)]
65struct IncompleteVigenere {
66    key: Option<String>,
67    alphabet: Option<Alphabet>,
68}
69
70impl VigenereBuilder for anyhow::Result<IncompleteVigenere> {
71    fn key<T: AsRef<str>>(self, key: T) -> impl VigenereBuilder {
72        if let Ok(mut vigenere) = self {
73            vigenere.key = Some(key.as_ref().to_owned());
74            Ok(vigenere)
75        } else {
76            self
77        }
78    }
79
80    fn alphabet<T: AsRef<str>>(self, alphabet: T) -> impl VigenereBuilder {
81        if let Ok(mut vigenere) = self {
82            vigenere.alphabet = Some(Alphabet::caseless(alphabet.as_ref())?);
83            Ok(vigenere)
84        } else {
85            self
86        }
87    }
88
89    fn build(self) -> anyhow::Result<Vigenere> {
90        if let Ok(vigenere) = self {
91            let Some(key) = vigenere.key else {
92                anyhow::bail!("Error building Vigenere: No key provided.");
93            };
94
95            let Some(alphabet) = vigenere.alphabet else {
96                anyhow::bail!("Error building Vigenere: No alphabet provided.");
97            };
98
99            Ok(Vigenere { alphabet, key })
100        } else {
101            Err(self.unwrap_err())
102        }
103    }
104}
105
106impl Vigenere {
107    #[allow(clippy::new_ret_no_self)]
108    pub fn new() -> impl VigenereBuilder {
109        Ok(IncompleteVigenere::default())
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use crate::{Vigenere, VigenereBuilder as _};
116
117    #[test]
118    fn encrypt_decrypt() -> anyhow::Result<()> {
119        let plaintext = include_str!("../tests/letter.txt");
120        let ciphertext = include_str!("../tests/encrypted_letter.txt");
121
122        let vigenere = Vigenere::new().alphabet("AYCDWZIHGJKLQNOPMVSTXREUBF").key("MYSUPERTOPSECRETKEY").build()?;
123
124        assert_eq!(ciphertext, vigenere.encrypt(plaintext));
125        assert_eq!(plaintext, vigenere.decrypt(ciphertext));
126
127        Ok(())
128    }
129}