slack_hooked/
hex.rs

1use crate::error::{Error, ErrorKind};
2use std::{convert::TryFrom, str::FromStr};
3
4use hex::FromHex;
5use serde::Serialize;
6
7/// A `HexColor` `String` can be one of:
8///
9/// 1. `String`s: `good`, `warning`, `danger`
10/// 2. Any valid hex color code: e.g. `#b13d41` or `#000`.
11///
12/// hex color codes will be checked to ensure a valid hex number is provided
13#[derive(Serialize, Debug, Clone, PartialEq)]
14pub struct HexColor(String);
15impl HexColor {
16    fn new<S: Into<String>>(s: S) -> HexColor {
17        HexColor(s.into())
18    }
19}
20
21impl Default for HexColor {
22    fn default() -> HexColor {
23        HexColor::new("#000")
24    }
25}
26
27impl ::std::fmt::Display for HexColor {
28    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
29        write!(f, "{}", self.0)
30    }
31}
32
33impl TryFrom<&str> for HexColor {
34    type Error = Error;
35
36    fn try_from(s: &str) -> Result<Self, Self::Error> {
37        s.parse()
38    }
39}
40
41/// Default slack colors built-in to the API
42/// See: https://api.slack.com/docs/attachments
43#[derive(Copy, Clone, Debug, PartialEq, Eq)]
44pub enum SlackColor {
45    /// green
46    Good,
47    /// orange
48    Warning,
49    /// red
50    Danger,
51}
52
53const SLACK_COLORS: [&str; 3] = ["good", "warning", "danger"];
54
55impl ::std::fmt::Display for SlackColor {
56    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
57        write!(f, "{}", self.as_ref())
58    }
59}
60
61impl AsRef<str> for SlackColor {
62    fn as_ref(&self) -> &str {
63        match *self {
64            SlackColor::Good => "good",
65            SlackColor::Warning => "warning",
66            SlackColor::Danger => "danger",
67        }
68    }
69}
70
71impl From<SlackColor> for HexColor {
72    fn from(color: SlackColor) -> HexColor {
73        HexColor::new(color.to_string())
74    }
75}
76
77impl FromStr for HexColor {
78    type Err = Error;
79
80    fn from_str(s: &str) -> Result<Self, Self::Err> {
81        let s: String = s.into();
82        if SLACK_COLORS.contains(&&s[..]) {
83            return Ok(HexColor(s));
84        }
85
86        let num_chars = s.chars().count();
87        if num_chars != 7 && num_chars != 4 {
88            return Err(ErrorKind::HexColor(format!(
89                "Must be 4 or 7 characters long (including #): \
90                 found `{}`",
91                s
92            ))
93            .into());
94        }
95        if !s.starts_with('#') {
96            return Err(ErrorKind::HexColor(format!("No leading #: found `{}`", s)).into());
97        }
98
99        // #d18 -> #dd1188
100        let hex = if num_chars == 4 {
101            s.chars().skip(1).fold(String::from("#"), |mut s, c| {
102                s.push(c);
103                s.push(c);
104                s
105            })
106        } else {
107            s.clone()
108        };
109
110        // see if the remaining part of the string is actually hex
111        match Vec::from_hex(&hex[1..]) {
112            Ok(_) => Ok(HexColor::new(s)),
113            Err(e) => Err(e.into()),
114        }
115    }
116}
117
118#[cfg(test)]
119mod test {
120    use super::*;
121    use crate::HexColor;
122    use std::convert::TryFrom;
123
124    #[test]
125    fn test_hex_color_too_short() {
126        let err = HexColor::try_from("abc").unwrap_err();
127        assert_eq!(
128            err.to_string(),
129            "hex color parsing error: Must be 4 or 7 characters long (including #): found \
130             `abc`"
131        );
132    }
133
134    #[test]
135    fn test_hex_color_missing_hash() {
136        let err = HexColor::try_from("1234567").unwrap_err();
137        assert_eq!(
138            err.to_string(),
139            "hex color parsing error: No leading #: found `1234567`"
140        );
141    }
142
143    #[test]
144    fn test_hex_color_invalid_hex_fmt() {
145        let err = HexColor::try_from("#abc12z").unwrap_err();
146        assert!(err
147            .to_string()
148            .contains("Invalid character 'z' at position 5"));
149    }
150
151    #[test]
152    fn test_hex_color_good() {
153        let h: HexColor = HexColor::try_from(SlackColor::Good).unwrap();
154        assert_eq!(h.to_string(), "good");
155    }
156
157    #[test]
158    fn test_hex_color_danger_str() {
159        let ok = HexColor::try_from("danger").unwrap();
160        assert_eq!(ok.to_string(), "danger");
161    }
162
163    #[test]
164    fn test_hex_color_3_char_hex() {
165        let ok = HexColor::try_from("#d18").unwrap();
166        assert_eq!(ok.to_string(), "#d18");
167    }
168
169    #[test]
170    fn test_hex_color_valid_upper_hex() {
171        let ok = HexColor::try_from("#103D18").unwrap();
172        assert_eq!(ok.to_string(), "#103D18");
173    }
174
175    #[test]
176    fn test_hex_color_valid_lower_hex() {
177        let ok = HexColor::try_from("#103d18").unwrap();
178        assert_eq!(ok.to_string(), "#103d18");
179    }
180}