pub struct Caesar {
rot: u8,
}
impl Caesar {
pub fn new(rot: u8) -> Result<Self, String> {
if !(1..=26).contains(&rot) {
Err(String::from("Rotation must be between 1 through 26"))
} else {
Ok(Caesar { rot })
}
}
pub fn encipher(&self, plaintext: &str) -> Result<String, &'static str> {
Caesar::shift(plaintext, self.rot)
}
pub fn decipher(&self, ciphertext: &str) -> Result<String, &'static str> {
let rot = 26 - self.rot;
Caesar::shift(ciphertext, rot)
}
fn shift(text: &str, rot: u8) -> Result<String, &'static str> {
Ok(text
.chars()
.map(|c| match c as u8 {
65..=90 => (((c as u8 - 65 + rot) % 26) + 65) as char,
97..=122 => (((c as u8 - 97 + rot) % 26) + 97) as char,
_ => c,
})
.collect::<String>())
}
}
#[cfg(test)]
mod tests {
use super::Caesar;
#[test]
fn encipher() {
let c = Caesar::new(13).unwrap();
assert_eq!("guvf vf n frperg", c.encipher("this is a secret").unwrap());
}
#[test]
fn decipher() {
let c = Caesar::new(10).unwrap();
assert_eq!("this is a secret", c.decipher("drsc sc k combod").unwrap());
}
#[test]
fn all_rotations() {
let alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
for n in 1..27 {
let c = Caesar::new(n).unwrap();
assert_eq!(alpha, c.decipher(&c.encipher(alpha).unwrap()).unwrap());
}
}
#[test]
fn with_punctuation() {
let c = Caesar::new(7).unwrap();
assert_eq!(
"Olssv, P ohcl h zljyla",
c.encipher("Hello, I have a secret").unwrap()
);
}
#[test]
fn with_unicode() {
let c = Caesar::new(9).unwrap();
assert_eq!(
"R 🖤 lahycxpajyqh",
c.encipher("I 🖤 cryptography").unwrap()
);
}
#[test]
fn too_low_rotation() {
assert!(Caesar::new(0).is_err());
}
#[test]
fn too_high_rotation() {
assert!(Caesar::new(27).is_err());
}
}