Skip to content

Commit d63b70b

Browse files
committed
refactor: optimize error handling
Change error handling to result instead of simple string, which is more standardized and can handle more types of errors.
1 parent ddb757a commit d63b70b

File tree

10 files changed

+382
-261
lines changed

10 files changed

+382
-261
lines changed

src/base/text/decode.rs

Lines changed: 39 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,55 @@
1-
pub fn base16_decode(input: &str) -> String {
2-
const BASE16_CHARS: &str = "0123456789abcdef";
3-
let mut char_to_index = [255; 256];
4-
for (i, c) in BASE16_CHARS.chars().enumerate() {
1+
pub fn base16_decode(input: &str) -> Result<String, String> {
2+
const BASE16_CHARS: &[u8] = b"0123456789abcdef";
3+
let mut char_to_index = [255u8; 256];
4+
for (i, &c) in BASE16_CHARS.iter().enumerate() {
55
char_to_index[c as usize] = (i % 16) as u8;
66
}
77

88
if input.len() % 2 != 0 {
9-
return String::from("Invalid base16 input: length is not even");
9+
return Err("Invalid base16 input: length is not even".to_string());
1010
}
1111

12-
let mut result = Vec::new();
12+
let mut result = Vec::with_capacity(input.len() / 2);
1313

1414
for (i, chunk) in input.as_bytes().chunks(2).enumerate() {
1515
if chunk.len() != 2 {
16-
return format!(
16+
return Err(format!(
1717
"Invalid base16 input: chunk at index {} has invalid length",
1818
i * 2
19-
);
19+
));
2020
}
2121

22-
let high_nibble = match char_to_index[chunk[0] as usize] {
23-
0..=15 => char_to_index[chunk[0] as usize],
24-
_ => {
25-
return format!(
26-
"Invalid base16 input: invalid character '{}' at index {}",
27-
char::from(chunk[0]),
28-
i * 2
29-
)
30-
}
31-
};
32-
33-
let low_nibble = match char_to_index[chunk[1] as usize] {
34-
0..=15 => char_to_index[chunk[1] as usize],
35-
_ => {
36-
return format!(
37-
"Invalid base16 input: invalid character '{}' at index {}",
38-
char::from(chunk[1]),
39-
i * 2 + 1
40-
)
41-
}
42-
};
22+
let high_nibble = char_to_index[chunk[0] as usize];
23+
if high_nibble == 255 {
24+
return Err(format!(
25+
"Invalid base16 input: invalid character '{}' at index {}",
26+
char::from(chunk[0]),
27+
i * 2
28+
));
29+
}
30+
31+
let low_nibble = char_to_index[chunk[1] as usize];
32+
if low_nibble == 255 {
33+
return Err(format!(
34+
"Invalid base16 input: invalid character '{}' at index {}",
35+
char::from(chunk[1]),
36+
i * 2 + 1
37+
));
38+
}
4339

4440
let byte = (high_nibble << 4) | low_nibble;
4541
result.push(byte);
4642
}
4743

48-
String::from_utf8(result)
49-
.unwrap_or_else(|_| String::from("Invalid UTF-8 sequence in decoded data"))
44+
String::from_utf8(result).map_err(|_| "Invalid UTF-8 sequence in decoded data".to_string())
5045
}
5146

52-
pub fn base64_decode(input: &str) -> String {
53-
const BASE64_CHARS: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
47+
pub fn base64_decode(input: &str) -> Result<String, String> {
48+
const BASE64_CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
5449
let mut result = Vec::new();
5550

5651
if input.len() % 4 != 0 {
57-
return String::from("Invalid Base64 input.");
52+
return Err("Invalid Base64 input: length not divisible by 4".to_string());
5853
}
5954

6055
let chunks: Vec<_> = input.as_bytes().chunks(4).collect();
@@ -63,7 +58,7 @@ pub fn base64_decode(input: &str) -> String {
6358
let is_last_chunk = chunk_idx == chunks.len() - 1;
6459

6560
if chunk.len() != 4 {
66-
return String::from("Invalid Base64 input.");
61+
return Err("Invalid Base64 input: incomplete chunk".to_string());
6762
}
6863

6964
let mut values = [0u8; 4];
@@ -74,31 +69,27 @@ pub fn base64_decode(input: &str) -> String {
7469

7570
if c == '=' {
7671
if !is_last_chunk || i < 2 {
77-
return String::from("Invalid Base64 input.");
72+
return Err("Invalid Base64 padding".to_string());
7873
}
7974
pad_count += 1;
8075
values[i] = 0;
8176
} else {
82-
match BASE64_CHARS.find(c) {
77+
match BASE64_CHARS.iter().position(|&b| b == byte) {
8378
Some(idx) => values[i] = idx as u8,
84-
None => return String::from("Invalid Base64 input."),
85-
}
86-
87-
if is_last_chunk && pad_count > 0 {
88-
return String::from("Invalid Base64 input.");
79+
None => return Err(format!("Invalid Base64 character: '{}'", c)),
8980
}
9081
}
9182
}
9283

9384
if pad_count > 2 {
94-
return String::from("Invalid Base64 input.");
85+
return Err("Too many padding characters".to_string());
9586
}
9687

9788
if is_last_chunk {
9889
match pad_count {
99-
1 if chunk[3] != b'=' => return String::from("Invalid Base64 input."),
90+
1 if chunk[3] != b'=' => return Err("Invalid padding position".to_string()),
10091
2 if chunk[2] != b'=' || chunk[3] != b'=' => {
101-
return String::from("Invalid Base64 input.")
92+
return Err("Invalid padding position".to_string())
10293
}
10394
_ => (),
10495
}
@@ -117,12 +108,9 @@ pub fn base64_decode(input: &str) -> String {
117108
}
118109
}
119110

120-
String::from_utf8(result)
121-
.unwrap_or_else(|_| String::from("Invalid UTF-8 sequence in decoded data"))
111+
String::from_utf8(result).map_err(|_| "Invalid UTF-8 sequence in decoded data".to_string())
122112
}
123113

124-
pub fn text_decode(input: &str) -> [String; 2] {
125-
let base16 = base16_decode(input);
126-
let base64 = base64_decode(input);
127-
[base16, base64]
114+
pub fn text_decode(input: &str) -> [Result<String, String>; 2] {
115+
[base16_decode(input), base64_decode(input)]
128116
}

src/base/text/encode.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
pub fn base16_encode(input: &str) -> String {
1+
use std::string::FromUtf8Error;
2+
3+
pub fn base16_encode(input: &str) -> Result<String, FromUtf8Error> {
24
const BASE16_CHARS: &[u8] = b"0123456789abcdef";
35

46
let bytes = input.as_bytes();
@@ -9,10 +11,10 @@ pub fn base16_encode(input: &str) -> String {
911
result.push(BASE16_CHARS[(byte & 0x0F) as usize]);
1012
}
1113

12-
String::from_utf8(result).unwrap()
14+
String::from_utf8(result)
1315
}
1416

15-
pub fn base64_encode(input: &str) -> String {
17+
pub fn base64_encode(input: &str) -> Result<String, FromUtf8Error> {
1618
const BASE64_CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1719
let bytes = input.as_bytes();
1820
let mut result = Vec::with_capacity(((bytes.len() + 2) / 3) * 4);
@@ -52,11 +54,11 @@ pub fn base64_encode(input: &str) -> String {
5254
}
5355
}
5456

55-
String::from_utf8_lossy(&result).to_string()
57+
String::from_utf8(result)
5658
}
5759

58-
pub fn text_encode(input: &str) -> [String; 2] {
59-
let base16 = base16_encode(input);
60-
let base64 = base64_encode(input);
61-
[base16, base64]
60+
pub fn text_encode(input: &str) -> Result<[String; 2], FromUtf8Error> {
61+
let base16 = base16_encode(input)?;
62+
let base64 = base64_encode(input)?;
63+
Ok([base16, base64])
6264
}

src/base/text/test.rs

Lines changed: 77 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,94 @@
11
#[cfg(test)]
22
mod tests {
3-
use crate::base;
3+
use crate::base::text::decode::{base16_decode, base64_decode, text_decode};
4+
use crate::base::text::encode::{base16_encode, base64_encode, text_encode};
5+
6+
#[test]
7+
fn test_base16_encode() {
8+
assert_eq!(base16_encode("wutong").unwrap(), "7775746f6e67");
9+
assert_eq!(base16_encode("WUTONG").unwrap(), "5755544f4e47");
10+
assert_eq!(base16_encode("1234567890").unwrap(), "31323334353637383930");
11+
assert_eq!(base16_encode("").unwrap(), "");
12+
}
13+
14+
#[test]
15+
fn test_base64_encode() {
16+
assert_eq!(base64_encode("wutong").unwrap(), "d3V0b25n");
17+
assert_eq!(base64_encode("WUTONG").unwrap(), "V1VUT05H");
18+
assert_eq!(base64_encode("1234567890").unwrap(), "MTIzNDU2Nzg5MA==");
19+
assert_eq!(base64_encode("").unwrap(), "");
20+
}
421

522
#[test]
623
fn test_base16_decode() {
7-
assert_eq!(
8-
base::text::decode::base16_decode("7775746f6e67"),
9-
"wutong".to_string()
10-
);
11-
assert_eq!(
12-
base::text::decode::base16_decode("5755544f4e47"),
13-
"WUTONG".to_string()
14-
);
15-
assert_eq!(
16-
base::text::decode::base16_decode("31323334353637383930"),
17-
"1234567890".to_string()
18-
);
19-
assert_eq!(base::text::decode::base16_decode(""), "".to_string());
24+
assert_eq!(base16_decode("7775746f6e67").unwrap(), "wutong");
25+
assert_eq!(base16_decode("5755544f4e47").unwrap(), "WUTONG");
26+
assert_eq!(base16_decode("31323334353637383930").unwrap(), "1234567890");
27+
assert_eq!(base16_decode("").unwrap(), "");
28+
assert!(base16_decode("123").is_err());
29+
assert!(base16_decode("12345").is_err());
30+
assert!(base16_decode("123G").is_err());
2031
}
2132

2233
#[test]
2334
fn test_base64_decode() {
24-
assert_eq!(
25-
base::text::decode::base64_decode("d3V0b25n"),
26-
"wutong".to_string()
27-
);
28-
assert_eq!(
29-
base::text::decode::base64_decode("V1VUT05H"),
30-
"WUTONG".to_string()
31-
);
32-
assert_eq!(
33-
base::text::decode::base64_decode("MTIzNDU2Nzg5MA=="),
34-
"1234567890".to_string()
35-
);
36-
assert_eq!(base::text::decode::base64_decode(""), "".to_string());
35+
assert_eq!(base64_decode("d3V0b25n").unwrap(), "wutong");
36+
assert_eq!(base64_decode("V1VUT05H").unwrap(), "WUTONG");
37+
assert_eq!(base64_decode("MTIzNDU2Nzg5MA==").unwrap(), "1234567890");
38+
assert_eq!(base64_decode("").unwrap(), "");
39+
assert!(base64_decode("ABC").is_err());
40+
assert!(base64_decode("ABCD==").is_err());
41+
assert!(base64_decode("ABCD=").is_err());
42+
assert!(base64_decode("ABCG").is_err());
3743
}
3844

3945
#[test]
40-
fn test_base16_encode() {
41-
assert_eq!(
42-
base::text::encode::base16_encode("wutong"),
43-
"7775746f6e67".to_string()
44-
);
45-
assert_eq!(
46-
base::text::encode::base16_encode("WUTONG"),
47-
"5755544f4e47".to_string()
48-
);
49-
assert_eq!(
50-
base::text::encode::base16_encode("1234567890"),
51-
"31323334353637383930".to_string()
52-
);
53-
assert_eq!(base::text::encode::base16_encode(""), "".to_string());
46+
fn test_text_encode() {
47+
let [base16, base64] = text_encode("wutong").unwrap();
48+
assert_eq!(base16, "7775746f6e67");
49+
assert_eq!(base64, "d3V0b25n");
50+
51+
let [base16, base64] = text_encode("WUTONG").unwrap();
52+
assert_eq!(base16, "5755544f4e47");
53+
assert_eq!(base64, "V1VUT05H");
54+
55+
let [base16, base64] = text_encode("1234567890").unwrap();
56+
assert_eq!(base16, "31323334353637383930");
57+
assert_eq!(base64, "MTIzNDU2Nzg5MA==");
58+
59+
let [base16, base64] = text_encode("").unwrap();
60+
assert_eq!(base16, "");
61+
assert_eq!(base64, "");
5462
}
5563

5664
#[test]
57-
fn test_base64_encode() {
58-
assert_eq!(
59-
base::text::encode::base64_encode("wutong"),
60-
"d3V0b25n".to_string()
61-
);
62-
assert_eq!(
63-
base::text::encode::base64_encode("WUTONG"),
64-
"V1VUT05H".to_string()
65-
);
66-
assert_eq!(
67-
base::text::encode::base64_encode("1234567890"),
68-
"MTIzNDU2Nzg5MA==".to_string()
69-
);
70-
assert_eq!(base::text::encode::base64_encode(""), "".to_string());
65+
fn test_text_decode() {
66+
let [base16_res, base64_res] = text_decode("7775746f6e67");
67+
assert_eq!(base16_res.unwrap(), "wutong");
68+
assert!(base64_res.is_err());
69+
70+
let [base16_res, base64_res] = text_decode("d3V0b25n");
71+
assert!(base16_res.is_err());
72+
assert_eq!(base64_res.unwrap(), "wutong");
73+
74+
let [base16_res, base64_res] = text_decode("5755544f4e47");
75+
assert_eq!(base16_res.unwrap(), "WUTONG");
76+
assert!(base64_res.is_err());
77+
78+
let [base16_res, base64_res] = text_decode("V1VUT05H");
79+
assert!(base16_res.is_err());
80+
assert_eq!(base64_res.unwrap(), "WUTONG");
81+
82+
let [base16_res, base64_res] = text_decode("31323334353637383930");
83+
assert_eq!(base16_res.unwrap(), "1234567890");
84+
assert!(base64_res.is_err());
85+
86+
let [base16_res, base64_res] = text_decode("MTIzNDU2Nzg5MA==");
87+
assert!(base16_res.is_err());
88+
assert_eq!(base64_res.unwrap(), "1234567890");
89+
90+
let [base16_res, base64_res] = text_decode("");
91+
assert_eq!(base16_res.unwrap(), "");
92+
assert_eq!(base64_res.unwrap(), "");
7193
}
7294
}

src/base_conversion/math.rs

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,38 @@
1-
pub fn binary(input: &str) -> [String; 3] {
1+
use std::num::ParseIntError;
2+
3+
pub fn binary(input: &str) -> Result<[String; 3], ParseIntError> {
24
convert_base(input, 2, [8, 10, 16])
35
}
46

5-
pub fn octal(input: &str) -> [String; 3] {
7+
pub fn octal(input: &str) -> Result<[String; 3], ParseIntError> {
68
convert_base(input, 8, [2, 10, 16])
79
}
810

9-
pub fn decimal(input: &str) -> [String; 3] {
11+
pub fn decimal(input: &str) -> Result<[String; 3], ParseIntError> {
1012
convert_base(input, 10, [2, 8, 16])
1113
}
1214

13-
pub fn hexadecimal(input: &str) -> [String; 3] {
15+
pub fn hexadecimal(input: &str) -> Result<[String; 3], ParseIntError> {
1416
convert_base(input, 16, [2, 8, 10])
1517
}
1618

17-
fn convert_base(input: &str, input_radix: u32, output_radixes: [u32; 3]) -> [String; 3] {
18-
let value = u64::from_str_radix(input, input_radix).unwrap_or(0);
19+
fn convert_base(
20+
input: &str,
21+
input_radix: u32,
22+
output_radixes: [u32; 3],
23+
) -> Result<[String; 3], ParseIntError> {
24+
let value = u64::from_str_radix(input, input_radix)?;
25+
let mut results = [String::new(), String::new(), String::new()];
26+
27+
for (i, radix) in output_radixes.iter().copied().enumerate() {
28+
results[i] = match radix {
29+
2 => format!("{:b}", value),
30+
8 => format!("{:o}", value),
31+
10 => value.to_string(),
32+
16 => format!("{:x}", value),
33+
_ => unreachable!(),
34+
};
35+
}
1936

20-
output_radixes.map(|radix| match radix {
21-
2 => format!("{:b}", value),
22-
8 => format!("{:o}", value),
23-
10 => value.to_string(),
24-
16 => format!("{:x}", value),
25-
_ => unreachable!(),
26-
})
37+
Ok(results)
2738
}

0 commit comments

Comments
 (0)