use crate::config::{ALPHABET_SIZE, LOWERCASE_BASE, MAX_SHIFT, UPPERCASE_BASE};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CipherError {
InvalidShift(String),
EmptyText,
}
impl std::fmt::Display for CipherError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
CipherError::InvalidShift(msg) => write!(f, "Invalid shift value: {}", msg),
CipherError::EmptyText => write!(f, "Input text cannot be empty or whitespace-only"),
}
}
}
impl std::error::Error for CipherError {}
pub fn encrypt(text: &str, shift: i16) -> String {
shift_text(text, shift)
}
pub fn decrypt(text: &str, shift: i16) -> String {
let negated = -(shift as i32);
let normalized = negated.rem_euclid(ALPHABET_SIZE as i32) as i16;
shift_text(text, normalized)
}
pub fn encrypt_safe(text: &str, shift: i16) -> Result<String, CipherError> {
validate_safe_inputs(text, shift)?;
Ok(shift_text(text, shift))
}
pub fn decrypt_safe(text: &str, shift: i16) -> Result<String, CipherError> {
validate_safe_inputs(text, shift)?;
Ok(decrypt(text, shift))
}
fn validate_safe_inputs(text: &str, shift: i16) -> Result<(), CipherError> {
if text.trim().is_empty() {
return Err(CipherError::EmptyText);
}
if !(-MAX_SHIFT..=MAX_SHIFT).contains(&shift) {
return Err(invalid_shift_error(shift));
}
Ok(())
}
fn invalid_shift_error(shift: i16) -> CipherError {
CipherError::InvalidShift(format!(
"Shift value {} is out of range (-{} to {})",
shift, MAX_SHIFT, MAX_SHIFT
))
}
fn shift_text(text: &str, shift: i16) -> String {
let normalized_shift = shift.rem_euclid(ALPHABET_SIZE);
text.chars()
.map(|c| match c {
'A'..='Z' => {
let shifted =
(c as i16 - UPPERCASE_BASE + normalized_shift).rem_euclid(ALPHABET_SIZE);
((shifted + UPPERCASE_BASE) as u8) as char
}
'a'..='z' => {
let shifted =
(c as i16 - LOWERCASE_BASE + normalized_shift).rem_euclid(ALPHABET_SIZE);
((shifted + LOWERCASE_BASE) as u8) as char
}
_ => c,
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encrypt_uppercase() {
assert_eq!(encrypt("ABC", 1), "BCD");
assert_eq!(encrypt("XYZ", 3), "ABC");
assert_eq!(encrypt("HELLO", 3), "KHOOR");
}
#[test]
fn test_encrypt_lowercase() {
assert_eq!(encrypt("abc", 1), "bcd");
assert_eq!(encrypt("xyz", 3), "abc");
assert_eq!(encrypt("hello", 3), "khoor");
}
#[test]
fn test_encrypt_mixed_case() {
assert_eq!(encrypt("Hello World", 3), "Khoor Zruog");
assert_eq!(encrypt("AbC", 1), "BcD");
}
#[test]
fn test_encrypt_with_non_alphabetic() {
assert_eq!(encrypt("Hello, World! 123", 3), "Khoor, Zruog! 123");
assert_eq!(encrypt("Test@#$", 5), "Yjxy@#$");
}
#[test]
fn test_decrypt() {
assert_eq!(decrypt("BCD", 1), "ABC");
assert_eq!(decrypt("ABC", 3), "XYZ");
assert_eq!(decrypt("KHOOR", 3), "HELLO");
}
#[test]
fn test_encrypt_decrypt_roundtrip() {
let original = "Hello World! 123";
let shift = 7;
let encrypted = encrypt(original, shift);
let decrypted = decrypt(&encrypted, shift);
assert_eq!(original, decrypted);
}
#[test]
fn test_negative_shift() {
assert_eq!(encrypt("ABC", -1), "ZAB");
assert_eq!(encrypt("abc", -1), "zab");
}
#[test]
fn test_large_shift() {
assert_eq!(encrypt("ABC", 26), "ABC");
assert_eq!(encrypt("ABC", 27), "BCD");
assert_eq!(encrypt("ABC", -26), "ABC");
}
#[test]
fn test_encrypt_safe_valid() {
assert_eq!(encrypt_safe("Hello", 3).unwrap(), "Khoor");
assert_eq!(encrypt_safe("Test", 25).unwrap(), "Sdrs");
assert_eq!(encrypt_safe("Test", -25).unwrap(), "Uftu");
}
#[test]
fn test_encrypt_safe_empty_text() {
assert!(matches!(encrypt_safe("", 3), Err(CipherError::EmptyText)));
}
#[test]
fn test_encrypt_safe_invalid_shift() {
assert!(matches!(
encrypt_safe("Test", 26),
Err(CipherError::InvalidShift(_))
));
assert!(matches!(
encrypt_safe("Test", -26),
Err(CipherError::InvalidShift(_))
));
assert!(matches!(
encrypt_safe("Test", 100),
Err(CipherError::InvalidShift(_))
));
}
#[test]
fn test_decrypt_safe() {
assert_eq!(decrypt_safe("Khoor", 3).unwrap(), "Hello");
assert!(matches!(decrypt_safe("", 3), Err(CipherError::EmptyText)));
assert!(matches!(
decrypt_safe("Test", 26),
Err(CipherError::InvalidShift(_))
));
}
#[test]
fn test_zero_shift() {
let text = "Hello World";
assert_eq!(encrypt(text, 0), text);
assert_eq!(decrypt(text, 0), text);
}
#[test]
fn test_japanese_characters() {
let text = "こんにちは";
assert_eq!(encrypt(text, 3), text);
}
#[test]
fn test_special_characters() {
let text = "!@#$%^&*()";
assert_eq!(encrypt(text, 5), text);
}
}