1use std::{fmt, num::ParseIntError};
2
3#[derive(Debug)]
4pub struct Color {
5 pub red: u8,
6 pub green: u8,
7 pub blue: u8,
8}
9
10pub enum HexError {
11 OddLength,
12 Empty,
13 Invalid,
14}
15
16impl fmt::Display for HexError {
17 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
18 match self {
19 HexError::OddLength => {
20 "Invalid hexadecimal color code representation. hexcode has odd number of bytes"
21 .fmt(f)
22 }
23 HexError::Empty => "hexcode is empty.".fmt(f),
24 HexError::Invalid => "Invalid hexadecimal color code representation.".fmt(f),
25 }
26 }
27}
28
29impl Color {
30 pub fn new(hex_code: &str) -> Result<Color, String> {
31 if hex_code.is_empty() {
32 return Err(HexError::Empty.to_string());
33 }
34
35 let hex_code = if hex_code.starts_with('#') {
37 crop_letters(hex_code, 1)
38 } else {
39 hex_code
40 };
41
42 let hex_code = if hex_code.len() == 3 {
44 repeat_letters(hex_code, 1)
45 } else {
46 hex_code.to_owned()
47 };
48
49 if hex_code.len() % 2 != 0 {
50 return Err(HexError::Invalid.to_string());
51 }
52
53 let decoded_values = decode_hex(&hex_code).unwrap_or_default();
54 if decoded_values.is_empty() || decoded_values.len() > 4 {
55 return Err(HexError::Invalid.to_string());
56 }
57
58 let color = Color {
59 red: decoded_values[0],
60 green: decoded_values[1],
61 blue: decoded_values[2],
62 };
63
64 Ok(color)
65 }
66}
67
68impl PartialEq for Color {
69 fn eq(&self, other: &Color) -> bool {
70 self.red == other.red && self.green == other.green && self.blue == other.blue
71 }
72}
73
74fn crop_letters(s: &str, pos: usize) -> &str {
76 match s.char_indices().nth(pos) {
77 Some((pos, _)) => &s[pos..],
78 None => "",
79 }
80}
81
82fn repeat_letters(s: &str, repetitions: i32) -> String {
84 let mut output = String::from("");
85 for char in s.chars() {
86 for _ in 0..=repetitions {
87 output.push(char);
88 }
89 }
90
91 output
92}
93
94fn decode_hex(s: &str) -> Result<Vec<u8>, ParseIntError> {
96 (0..s.len())
97 .step_by(2)
98 .map(|i| u8::from_str_radix(&s[i..i + 2], 16))
99 .collect()
100}
101
102pub fn convert_hexcode_to_rgb(hex_code: String) -> Result<Color, String> {
104 match Color::new(&hex_code) {
105 Ok(color) => Ok(color),
106 Err(e) => Err(e),
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 fn vec_compare<T: std::cmp::PartialEq>(va: &[T], vb: &[T]) -> bool {
115 (va.len() == vb.len()) && va.iter()
117 .zip(vb)
118 .all(|(a,b)| *a == *b)
119 }
120
121 #[test]
122 fn should_convert_hexcode_to_rgb() {
123 let hex = String::from("#ffffff");
124 let expected_color: Color = Color {
125 red: 255,
126 green: 255,
127 blue: 255,
128 };
129
130 assert_eq!(expected_color, convert_hexcode_to_rgb(hex).unwrap())
131 }
132
133 #[test]
134 fn should_convert_shorthand_hexcode_to_rgb() {
135 let hex = String::from("#fff");
136 let expected_color: Color = Color {
137 red: 255,
138 green: 255,
139 blue: 255,
140 };
141
142 assert_eq!(expected_color, convert_hexcode_to_rgb(hex).unwrap())
143 }
144
145 #[test]
146
147 fn should_throw_error_when_hexcode_is_invalid() {
148 let hex = String::from("#fasfsfff");
149 let err = convert_hexcode_to_rgb(hex).unwrap_err();
150 assert_eq!(err, HexError::Invalid.to_string());
151 }
152
153 #[test]
154
155 fn should_throw_error_when_shorthand_hexcode_is_invalid() {
156 let hex = String::from("#a0g");
157 let err = convert_hexcode_to_rgb(hex).unwrap_err();
158 assert_eq!(err, HexError::Invalid.to_string());
159 }
160
161 #[test]
162
163 fn should_decode_hex() {
164 let decoded = decode_hex("ffffff").unwrap();
165 let expected: Vec<u8> = vec![255, 255, 255];
166 assert!(vec_compare(&expected, &decoded), true);
167 }
168
169 #[test]
170 fn should_repeat_letters_once() {
171 assert_eq!("ff33ff".to_owned(), repeat_letters("f3f", 1));
172 }
173
174 #[test]
175 fn should_crop_letters() {
176 assert_eq!("ello", crop_letters("hello", 1));
177 assert_eq!("fff", crop_letters("#fff", 1));
178 assert_eq!("word", crop_letters("longword", 4));
179 }
180}