1use crate::error::{SlackError, Result};
2
3
4#[derive(Serialize, Debug, Clone, PartialEq)]
9pub struct HexColor(String);
10impl HexColor {
11 fn new<S: Into<String>>(s: S) -> HexColor {
12 HexColor(s.into())
13 }
14}
15
16impl Default for HexColor {
17 fn default() -> HexColor {
18 HexColor::new("#000")
19 }
20}
21
22impl ::std::fmt::Display for HexColor {
23 fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
24 write!(f, "{}", self.0)
25 }
26}
27
28#[derive(Copy, Clone, Debug, PartialEq, Eq)]
31pub enum SlackColor {
32 Good,
34 Warning,
36 Danger,
38}
39
40const SLACK_COLORS: [&str; 3] = ["good", "warning", "danger"];
41
42impl ::std::fmt::Display for SlackColor {
43 fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
44 write!(f, "{}", self.as_ref())
45 }
46}
47
48impl AsRef<str> for SlackColor {
49 fn as_ref(&self) -> &str {
50 match *self {
51 SlackColor::Good => "good",
52 SlackColor::Warning => "warning",
53 SlackColor::Danger => "danger",
54 }
55 }
56}
57
58impl From<SlackColor> for HexColor {
59 fn from(color: SlackColor) -> HexColor {
60 HexColor::new(color.to_string())
61 }
62}
63
64impl From<SlackColor> for String {
65 fn from(color: SlackColor) -> String {
66 color.to_string()
67 }
68}
69
70impl HexColor {
71 pub fn new_checked<S: Into<String>>(s: S) -> Result<Self> {
77 let s: String = s.into();
78 if SLACK_COLORS.contains(&&s[..]) {
79 return Ok(HexColor(s));
80 }
81
82 let num_chars = s.chars().count();
83 if num_chars != 7 && num_chars != 4 {
84 return Err(SlackError::HexColor(format!(
85 "Must be 4 or 7 characters long (including #): \
86 found `{}`",
87 s
88 )));
89 }
90 if !s.starts_with('#') {
91 return Err(SlackError::HexColor(format!("No leading #: found `{}`", s)));
92 }
93
94 let hex = if num_chars == 4 {
96 s.chars().skip(1).fold(String::from("#"), |mut s, c| {
97 s.push(c);
98 s.push(c);
99 s
100 })
101 } else {
102 s.clone()
103 };
104
105 match hex::decode(&hex[1..]) {
107 Ok(_) => Ok(HexColor::new(s)),
108 Err(e) => Err(SlackError::HexColor(e.to_string())),
109 }
110 }
111}
112
113#[cfg(test)]
114mod test {
115 use super::*;
116 use crate::HexColor;
117
118 #[test]
119 fn test_hex_color_too_short() {
120 let err = HexColor::new_checked("abc").unwrap_err();
121 assert_eq!(
122 err.to_string(),
123 "hex color parsing error: Must be 4 or 7 characters long (including #): found `abc`"
124 );
125 }
126
127 #[test]
128 fn test_hex_color_missing_hash() {
129 let err = HexColor::new_checked("1234567").unwrap_err();
130 assert_eq!(
131 err.to_string(),
132 "hex color parsing error: No leading #: found `1234567`"
133 )
134 }
135
136 #[test]
137 fn test_hex_color_invalid_hex_fmt() {
138 let err = HexColor::new_checked("#abc12z").unwrap_err();
139 assert!(
140 err.to_string()
141 .contains("Invalid character 'z' at position 5")
142 );
143 }
144
145 #[test]
146 fn test_hex_color_good() {
147 let h: HexColor = HexColor::new_checked(SlackColor::Good).unwrap();
148 assert_eq!(h.to_string(), "good");
149 }
150
151 #[test]
152 fn test_hex_color_danger_str() {
153 let ok = HexColor::new_checked("danger").unwrap();
154 assert_eq!(ok.to_string(), "danger");
155 }
156
157 #[test]
158 fn test_hex_color_3_char_hex() {
159 let ok = HexColor::new_checked("#d18").unwrap();
160 assert_eq!(ok.to_string(), "#d18");
161 }
162
163 #[test]
164 fn test_hex_color_valid_upper_hex() {
165 let ok = HexColor::new_checked("#103D18").unwrap();
166 assert_eq!(ok.to_string(), "#103D18");
167 }
168
169 #[test]
170 fn test_hex_color_valid_lower_hex() {
171 let ok = HexColor::new_checked("#103d18").unwrap();
172 assert_eq!(ok.to_string(), "#103d18");
173 }
174}