Skip to content

Commit c16984f

Browse files
authored
Refactor Caesar Algorithm Implementation (#720)
* Updated Caesar Cipher to handle large inputs and rotations * Restored previously removed tests to ensure comprehensive test coverage. * Renamed Caesar function and make it public * Code formatting * Removed code example from function docs because it's breaking the tests * Removed unused trait that has not been warned by the `cargo clippy --all -- -D warnings` * Fix rust docs * Fix rust docs * Separate the cipher rotation on its own function * Added rotation range validation * Improvement in Caesar algorithm testing * Separated the caesar tests Dedicated macro_rule for happy path and error cases * Resolving requested changes Defined the alphabet length as a constant: `b'z' - b'a' + 1` * Resolved requested changes * Removed unecessary tests
1 parent 142fd12 commit c16984f

File tree

1 file changed

+98
-23
lines changed

1 file changed

+98
-23
lines changed

src/ciphers/caesar.rs

Lines changed: 98 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,118 @@
1-
//! Caesar Cipher
2-
//! Based on cipher_crypt::caesar
3-
//!
4-
//! # Algorithm
5-
//!
6-
//! Rotate each ascii character by shift. The most basic example is ROT 13, which rotates 'a' to
7-
//! 'n'. This implementation does not rotate unicode characters.
8-
9-
/// Caesar cipher to rotate cipher text by shift and return an owned String.
10-
pub fn caesar(cipher: &str, shift: u8) -> String {
11-
cipher
1+
const ERROR_MESSAGE: &str = "Rotation must be in the range [0, 25]";
2+
const ALPHABET_LENGTH: u8 = b'z' - b'a' + 1;
3+
4+
/// Encrypts a given text using the Caesar cipher technique.
5+
///
6+
/// In cryptography, a Caesar cipher, also known as Caesar's cipher, the shift cipher, Caesar's code,
7+
/// or Caesar shift, is one of the simplest and most widely known encryption techniques.
8+
/// It is a type of substitution cipher in which each letter in the plaintext is replaced by a letter
9+
/// some fixed number of positions down the alphabet.
10+
///
11+
/// # Arguments
12+
///
13+
/// * `text` - The text to be encrypted.
14+
/// * `rotation` - The number of rotations (shift) to be applied. It should be within the range [0, 25].
15+
///
16+
/// # Returns
17+
///
18+
/// Returns a `Result` containing the encrypted string if successful, or an error message if the rotation
19+
/// is out of the valid range.
20+
///
21+
/// # Errors
22+
///
23+
/// Returns an error if the rotation value is out of the valid range [0, 25]
24+
pub fn caesar(text: &str, rotation: isize) -> Result<String, &'static str> {
25+
if !(0..ALPHABET_LENGTH as isize).contains(&rotation) {
26+
return Err(ERROR_MESSAGE);
27+
}
28+
29+
let result = text
1230
.chars()
1331
.map(|c| {
1432
if c.is_ascii_alphabetic() {
15-
let first = if c.is_ascii_lowercase() { b'a' } else { b'A' };
16-
// modulo the distance to keep character range
17-
(first + (c as u8 + shift - first) % 26) as char
33+
shift_char(c, rotation)
1834
} else {
1935
c
2036
}
2137
})
22-
.collect()
38+
.collect();
39+
40+
Ok(result)
41+
}
42+
43+
/// Shifts a single ASCII alphabetic character by a specified number of positions in the alphabet.
44+
///
45+
/// # Arguments
46+
///
47+
/// * `c` - The ASCII alphabetic character to be shifted.
48+
/// * `rotation` - The number of positions to shift the character. Should be within the range [0, 25].
49+
///
50+
/// # Returns
51+
///
52+
/// Returns the shifted ASCII alphabetic character.
53+
fn shift_char(c: char, rotation: isize) -> char {
54+
let first = if c.is_ascii_lowercase() { b'a' } else { b'A' };
55+
let rotation = rotation as u8; // Safe cast as rotation is within [0, 25]
56+
57+
(((c as u8 - first) + rotation) % ALPHABET_LENGTH + first) as char
2358
}
2459

2560
#[cfg(test)]
2661
mod tests {
2762
use super::*;
2863

29-
#[test]
30-
fn empty() {
31-
assert_eq!(caesar("", 13), "");
64+
macro_rules! test_caesar_happy_path {
65+
($($name:ident: $test_case:expr,)*) => {
66+
$(
67+
#[test]
68+
fn $name() {
69+
let (text, rotation, expected) = $test_case;
70+
assert_eq!(caesar(&text, rotation).unwrap(), expected);
71+
72+
let backward_rotation = if rotation == 0 { 0 } else { ALPHABET_LENGTH as isize - rotation };
73+
assert_eq!(caesar(&expected, backward_rotation).unwrap(), text);
74+
}
75+
)*
76+
};
3277
}
3378

34-
#[test]
35-
fn caesar_rot_13() {
36-
assert_eq!(caesar("rust", 13), "ehfg");
79+
macro_rules! test_caesar_error_cases {
80+
($($name:ident: $test_case:expr,)*) => {
81+
$(
82+
#[test]
83+
fn $name() {
84+
let (text, rotation) = $test_case;
85+
assert_eq!(caesar(&text, rotation), Err(ERROR_MESSAGE));
86+
}
87+
)*
88+
};
3789
}
3890

3991
#[test]
40-
fn caesar_unicode() {
41-
assert_eq!(caesar("attack at dawn 攻", 5), "fyyfhp fy ifbs 攻");
92+
fn alphabet_length_should_be_26() {
93+
assert_eq!(ALPHABET_LENGTH, 26);
94+
}
95+
96+
test_caesar_happy_path! {
97+
empty_text: ("", 13, ""),
98+
rot_13: ("rust", 13, "ehfg"),
99+
unicode: ("attack at dawn 攻", 5, "fyyfhp fy ifbs 攻"),
100+
rotation_within_alphabet_range: ("Hello, World!", 3, "Khoor, Zruog!"),
101+
no_rotation: ("Hello, World!", 0, "Hello, World!"),
102+
rotation_at_alphabet_end: ("Hello, World!", 25, "Gdkkn, Vnqkc!"),
103+
longer: ("The quick brown fox jumps over the lazy dog.", 5, "Ymj vznhp gwtbs ktc ozrux tajw ymj qfed itl."),
104+
non_alphabetic_characters: ("12345!@#$%", 3, "12345!@#$%"),
105+
uppercase_letters: ("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1, "BCDEFGHIJKLMNOPQRSTUVWXYZA"),
106+
mixed_case: ("HeLlO WoRlD", 7, "OlSsV DvYsK"),
107+
with_whitespace: ("Hello, World!", 13, "Uryyb, Jbeyq!"),
108+
with_special_characters: ("Hello!@#$%^&*()_+World", 4, "Lipps!@#$%^&*()_+Asvph"),
109+
with_numbers: ("Abcd1234XYZ", 10, "Klmn1234HIJ"),
110+
}
111+
112+
test_caesar_error_cases! {
113+
negative_rotation: ("Hello, World!", -5),
114+
empty_input_negative_rotation: ("", -1),
115+
empty_input_large_rotation: ("", 27),
116+
large_rotation: ("Large rotation", 139),
42117
}
43118
}

0 commit comments

Comments
 (0)