-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcaesar_cipher.rs
More file actions
367 lines (335 loc) · 10.3 KB
/
caesar_cipher.rs
File metadata and controls
367 lines (335 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
//! # Caesar Cipher Module
//!
//! Provides easy-to-use Caesar cipher encryption and decryption.
//! Set text and shift number to encrypt or decrypt.
//!
//! # Usage
//!
//! ```
//! use caesar_cipher_enc_dec::caesar_cipher::{encrypt, decrypt, encrypt_safe, decrypt_safe};
//!
//! let text = "I Love You";
//! let enc_text = encrypt(&text, 3);
//! let dec_text = decrypt(&enc_text, 3);
//! let dec_text_2 = encrypt(&enc_text, -3);
//!
//! // Safe versions with error handling
//! match encrypt_safe(&text, 3) {
//! Ok(encrypted) => println!("Encrypted: {}", encrypted),
//! Err(e) => println!("Error: {}", e),
//! }
//! ```
//!
//! # Brute Force Example
//!
//! You can use this encrypt code for brute force decryption:
//!
//! ```
//! use caesar_cipher_enc_dec::caesar_cipher::encrypt;
//!
//! let text = "I Love You";
//! for i in 0..26 {
//! encrypt(&text, i);
//! }
//! ```
use crate::config::{ALPHABET_SIZE, LOWERCASE_BASE, MAX_SHIFT, UPPERCASE_BASE};
/// Error enum for Caesar cipher operations
///
/// This error type represents possible errors that can occur during encryption/decryption operations.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CipherError {
/// Error for invalid shift values
///
/// Occurs when the shift value is outside the range of -25 to 25.
InvalidShift(String),
/// Error for empty text input
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 {}
/// Encrypts text using Caesar cipher
///
/// This function encrypts the specified text with the given shift value.
/// Only alphabetic characters are encrypted, other characters remain unchanged.
///
/// # Arguments
///
/// * `text` - Text to encrypt
/// * `shift` - Shift value (positive for forward shift, negative for backward shift)
///
/// # Returns
///
/// Encrypted text
///
/// # Examples
///
/// ```
/// use caesar_cipher_enc_dec::caesar_cipher::encrypt;
///
/// let result = encrypt("Hello", 3);
/// assert_eq!(result, "Khoor");
/// ```
pub fn encrypt(text: &str, shift: i16) -> String {
shift_text(text, shift)
}
/// Decrypts text using Caesar cipher
///
/// This function decrypts the specified text with the given shift value.
/// Internally uses shift_text with a negative shift value.
///
/// The shift is widened to `i32` before negation to avoid integer overflow
/// when `shift == i16::MIN`; the normalized value passed to `shift_text` is
/// always within `0..ALPHABET_SIZE` so no precision is lost.
///
/// # Arguments
///
/// * `text` - Text to decrypt
/// * `shift` - Shift value to use for decryption
///
/// # Returns
///
/// Decrypted text
///
/// # Examples
///
/// ```
/// use caesar_cipher_enc_dec::caesar_cipher::decrypt;
///
/// let result = decrypt("Khoor", 3);
/// assert_eq!(result, "Hello");
/// ```
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)
}
/// Encrypts text using Caesar cipher with error handling
///
/// This function validates input values and returns an error for invalid inputs.
/// Returns errors for empty/whitespace-only text or out-of-range shift values
/// (outside -25 to 25).
///
/// # Arguments
///
/// * `text` - Text to encrypt (must not be empty or whitespace-only)
/// * `shift` - Shift value (must be in range -25 to 25)
///
/// # Returns
///
/// Encrypted text on success, `CipherError` on failure
///
/// # Errors
///
/// * `CipherError::EmptyText` - When text is empty or whitespace-only
/// * `CipherError::InvalidShift` - When shift value is out of range
///
/// # Examples
///
/// ```
/// use caesar_cipher_enc_dec::caesar_cipher::encrypt_safe;
///
/// let result = encrypt_safe("Hello", 3).unwrap();
/// assert_eq!(result, "Khoor");
///
/// // Error cases
/// assert!(encrypt_safe("", 3).is_err());
/// assert!(encrypt_safe("Hello", 26).is_err());
/// ```
pub fn encrypt_safe(text: &str, shift: i16) -> Result<String, CipherError> {
validate_safe_inputs(text, shift)?;
Ok(shift_text(text, shift))
}
/// Decrypts text using Caesar cipher with error handling
///
/// This function validates input values and returns an error for invalid inputs.
/// Validates shift range before negation to prevent integer overflow.
///
/// # Arguments
///
/// * `text` - Text to decrypt (must not be empty or whitespace-only)
/// * `shift` - Shift value to use for decryption (must be in range -25 to 25)
///
/// # Returns
///
/// Decrypted text on success, `CipherError` on failure
///
/// # Errors
///
/// * `CipherError::EmptyText` - When text is empty or whitespace-only
/// * `CipherError::InvalidShift` - When shift value is out of range
///
/// # Examples
///
/// ```
/// use caesar_cipher_enc_dec::caesar_cipher::decrypt_safe;
///
/// let result = decrypt_safe("Khoor", 3).unwrap();
/// assert_eq!(result, "Hello");
/// ```
pub fn decrypt_safe(text: &str, shift: i16) -> Result<String, CipherError> {
validate_safe_inputs(text, shift)?;
let negated = -(shift as i32);
let normalized = negated.rem_euclid(ALPHABET_SIZE as i32) as i16;
Ok(shift_text(text, normalized))
}
/// Validates shared inputs for safe Caesar cipher APIs.
///
/// This private function centralizes validation and error message generation
/// used by `encrypt_safe` and `decrypt_safe`.
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(())
}
/// Creates a standardized invalid shift error message.
fn invalid_shift_error(shift: i16) -> CipherError {
CipherError::InvalidShift(format!(
"Shift value {} is out of range (-{} to {})",
shift, MAX_SHIFT, MAX_SHIFT
))
}
/// Internal implementation: Performs text-level Caesar cipher transformation
///
/// This function handles the actual encryption processing.
/// Only alphabetic characters (A-Z, a-z) are transformed, other characters remain unchanged.
/// Shift values are automatically normalized to the 0-25 range using Euclidean modulo.
///
/// # Arguments
///
/// * `text` - Text to transform
/// * `shift` - Shift value (automatically normalized via `rem_euclid`)
///
/// # Returns
///
/// Transformed text
fn shift_text(text: &str, shift: i16) -> String {
// Use rem_euclid for proper handling of negative shifts
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);
}
}