1use crate::errors::InvalidColorError;
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5#[allow(clippy::exhaustive_enums)]
6pub enum Color {
7 Default,
9 LightWhite,
11 LightBlack,
13 LightBlue,
15 LightCyan,
17 LightGreen,
19 LightMagenta,
21 LightRed,
23 LightYellow,
25 LightGrey,
27 DarkWhite,
29 DarkBlack,
31 DarkBlue,
33 DarkCyan,
35 DarkGreen,
37 DarkMagenta,
39 DarkRed,
41 DarkYellow,
43 DarkGrey,
45 Index(u8),
47 Rgb {
49 red: u8,
51 green: u8,
53 blue: u8,
55 },
56}
57
58impl TryFrom<&str> for Color {
59 type Error = InvalidColorError;
60
61 #[allow(clippy::unwrap_in_result)]
62 #[inline]
63 fn try_from(s: &str) -> Result<Self, Self::Error> {
64 match s {
65 "black" | "light black" => Ok(Self::LightBlack),
66 "blue" | "light blue" => Ok(Self::LightBlue),
67 "cyan" | "light cyan" => Ok(Self::LightCyan),
68 "green" | "light green" => Ok(Self::LightGreen),
69 "magenta" | "light magenta" => Ok(Self::LightMagenta),
70 "red" | "light red" => Ok(Self::LightRed),
71 "white" | "light white" => Ok(Self::LightWhite),
72 "yellow" | "light yellow" => Ok(Self::LightYellow),
73 "grey" | "light grey" => Ok(Self::LightGrey),
74 "dark black" => Ok(Self::DarkBlack),
75 "dark blue" => Ok(Self::DarkBlue),
76 "dark cyan" => Ok(Self::DarkCyan),
77 "dark green" => Ok(Self::DarkGreen),
78 "dark magenta" => Ok(Self::DarkMagenta),
79 "dark red" => Ok(Self::DarkRed),
80 "dark white" => Ok(Self::DarkWhite),
81 "dark yellow" => Ok(Self::DarkYellow),
82 "dark grey" => Ok(Self::DarkGrey),
83 "transparent" | "-1" => Ok(Self::Default),
84 _ => {
85 let matches: Vec<&str> = s.split(',').collect();
86
87 match matches.len() {
88 1 => {
89 let color_index = s.parse::<u8>();
90 match color_index {
91 Ok(i) if (0..=255).contains(&i) => Ok(Self::Index(i)),
92 _ => Err(InvalidColorError::Indexed {}),
93 }
94 },
95 3 => {
96 let red = matches[0].parse::<i16>().unwrap_or(-1);
97 let green = matches[1].parse::<i16>().unwrap_or(-1);
98 let blue = matches[2].parse::<i16>().unwrap_or(-1);
99
100 if !(0..=255).contains(&red) {
101 return Err(InvalidColorError::Red {});
102 }
103
104 if !(0..=255).contains(&green) {
105 return Err(InvalidColorError::Green {});
106 }
107
108 if !(0..=255).contains(&blue) {
109 return Err(InvalidColorError::Blue {});
110 }
111
112 Ok(Self::Rgb {
113 red: red.try_into().unwrap(),
114 green: green.try_into().unwrap(),
115 blue: blue.try_into().unwrap(),
116 })
117 },
118 _ => Err(InvalidColorError::Invalid {}),
119 }
120 },
121 }
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use claim::assert_ok_eq;
128 use rstest::rstest;
129 use testutils::assert_err_eq;
130
131 use super::*;
132
133 #[rstest]
134 #[case::named_black("black", Color::LightBlack)]
135 #[case::named_light_black("light black", Color::LightBlack)]
136 #[case::named_dark_black("dark black", Color::DarkBlack)]
137 #[case::named_blue("blue", Color::LightBlue)]
138 #[case::named_light_blue("light blue", Color::LightBlue)]
139 #[case::named_dark_blue("dark blue", Color::DarkBlue)]
140 #[case::named_cyan("cyan", Color::LightCyan)]
141 #[case::named_light_cyan("light cyan", Color::LightCyan)]
142 #[case::named_dark_cyan("dark cyan", Color::DarkCyan)]
143 #[case::named_green("green", Color::LightGreen)]
144 #[case::named_light_green("light green", Color::LightGreen)]
145 #[case::named_dark_green("dark green", Color::DarkGreen)]
146 #[case::named_magenta("magenta", Color::LightMagenta)]
147 #[case::named_light_magenta("light magenta", Color::LightMagenta)]
148 #[case::named_dark_magenta("dark magenta", Color::DarkMagenta)]
149 #[case::named_red("red", Color::LightRed)]
150 #[case::named_light_red("light red", Color::LightRed)]
151 #[case::named_dark_red("dark red", Color::DarkRed)]
152 #[case::named_white("white", Color::LightWhite)]
153 #[case::named_yellow("yellow", Color::LightYellow)]
154 #[case::named_light_yellow("light yellow", Color::LightYellow)]
155 #[case::named_dark_yellow("dark yellow", Color::DarkYellow)]
156 #[case::index_0("0", Color::Index(0))]
157 #[case::index_255("255", Color::Index(255))]
158 #[case::rgb("100,101,102", Color::Rgb {
159 red: 100,
160 green: 101,
161 blue: 102
162 })]
163 fn try_from(#[case] color_string: &str, #[case] expected: Color) {
164 assert_ok_eq!(Color::try_from(color_string), expected);
165 }
166
167 #[rstest]
168 #[case::non_number_red("red,0,0", InvalidColorError::Red {})]
169 #[case::rgb_non_number_green("0,green,0", InvalidColorError::Green {})]
170 #[case::rgb_non_number_blue("0,0,blue", InvalidColorError::Blue {})]
171 #[case::rgb_non_number_red_lower_limit("-1,0,0", InvalidColorError::Red {})]
172 #[case::rgb_non_number_green_lower_limit("0,-1,0", InvalidColorError::Green {})]
173 #[case::rgb_non_number_blue_lower_limit("0,0,-1", InvalidColorError::Blue {})]
174 #[case::rgb_non_number_red_upper_limit("256,0,0", InvalidColorError::Red {})]
175 #[case::rgb_non_number_green_upper_limit("0,256,0", InvalidColorError::Green {})]
176 #[case::rgb_non_number_blue_upper_limit("0,0,256", InvalidColorError::Blue {})]
177 #[case::index_upper_limit("256", InvalidColorError::Indexed {})]
178 #[case::index_lower_limit("-2", InvalidColorError::Indexed {})]
179 #[case::str_single_value("invalid", InvalidColorError::Indexed {})]
180 #[case::str_multiple_value("invalid,invalid", InvalidColorError::Invalid {})]
181 fn color_try_from_fail(#[case] color_string: &str, #[case] expected: InvalidColorError) {
182 assert_err_eq!(Color::try_from(color_string), expected);
183 }
184}