slack_hook2/
hex.rs

1use crate::error::{SlackError, Result};
2
3
4/// A `HexColor` `String` can be one of:
5///
6/// 1. `String`s: `good`, `warning`, `danger`
7/// 2. Any valid hex color code: e.g. `#b13d41` or `#000`.
8#[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/// Default slack colors built-in to the API
29/// See: https://api.slack.com/docs/attachments
30#[derive(Copy, Clone, Debug, PartialEq, Eq)]
31pub enum SlackColor {
32    /// green
33    Good,
34    /// orange
35    Warning,
36    /// red
37    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    /// A checked constructor for HexColor
72    ///
73    /// Note that this used to be an `impl<S> TryFrom<S> for HexColor`,
74    /// but due to a conflicting blanket implementation, this has been changed.
75    /// https://github.com/rust-lang/rust/issues/50133
76    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        // #d18 -> #dd1188
95        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        // see if the remaining part of the string is actually hex
106        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}