use crate::error::{Error, ErrorKind};
use std::{convert::TryFrom, str::FromStr};
use hex::FromHex;
use serde::Serialize;
#[derive(Serialize, Debug, Clone, PartialEq)]
pub struct HexColor(String);
impl HexColor {
fn new<S: Into<String>>(s: S) -> HexColor {
HexColor(s.into())
}
}
impl Default for HexColor {
fn default() -> HexColor {
HexColor::new("#000")
}
}
impl ::std::fmt::Display for HexColor {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl TryFrom<&str> for HexColor {
type Error = Error;
fn try_from(s: &str) -> Result<Self, Self::Error> {
s.parse()
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum SlackColor {
Good,
Warning,
Danger,
}
const SLACK_COLORS: [&str; 3] = ["good", "warning", "danger"];
impl ::std::fmt::Display for SlackColor {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
write!(f, "{}", self.as_ref())
}
}
impl AsRef<str> for SlackColor {
fn as_ref(&self) -> &str {
match *self {
SlackColor::Good => "good",
SlackColor::Warning => "warning",
SlackColor::Danger => "danger",
}
}
}
impl From<SlackColor> for HexColor {
fn from(color: SlackColor) -> HexColor {
HexColor::new(color.to_string())
}
}
impl FromStr for HexColor {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s: String = s.into();
if SLACK_COLORS.contains(&&s[..]) {
return Ok(HexColor(s));
}
let num_chars = s.chars().count();
if num_chars != 7 && num_chars != 4 {
return Err(ErrorKind::HexColor(format!(
"Must be 4 or 7 characters long (including #): \
found `{}`",
s
))
.into());
}
if !s.starts_with('#') {
return Err(ErrorKind::HexColor(format!("No leading #: found `{}`", s)).into());
}
let hex = if num_chars == 4 {
s.chars().skip(1).fold(String::from("#"), |mut s, c| {
s.push(c);
s.push(c);
s
})
} else {
s.clone()
};
match Vec::from_hex(&hex[1..]) {
Ok(_) => Ok(HexColor::new(s)),
Err(e) => Err(e.into()),
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::HexColor;
use std::convert::TryFrom;
#[test]
fn test_hex_color_too_short() {
let err = HexColor::try_from("abc").unwrap_err();
assert_eq!(
err.to_string(),
"hex color parsing error: Must be 4 or 7 characters long (including #): found \
`abc`"
);
}
#[test]
fn test_hex_color_missing_hash() {
let err = HexColor::try_from("1234567").unwrap_err();
assert_eq!(
err.to_string(),
"hex color parsing error: No leading #: found `1234567`"
);
}
#[test]
fn test_hex_color_invalid_hex_fmt() {
let err = HexColor::try_from("#abc12z").unwrap_err();
assert!(err
.to_string()
.contains("Invalid character 'z' at position 5"));
}
#[test]
fn test_hex_color_good() {
let h: HexColor = HexColor::try_from(SlackColor::Good).unwrap();
assert_eq!(h.to_string(), "good");
}
#[test]
fn test_hex_color_danger_str() {
let ok = HexColor::try_from("danger").unwrap();
assert_eq!(ok.to_string(), "danger");
}
#[test]
fn test_hex_color_3_char_hex() {
let ok = HexColor::try_from("#d18").unwrap();
assert_eq!(ok.to_string(), "#d18");
}
#[test]
fn test_hex_color_valid_upper_hex() {
let ok = HexColor::try_from("#103D18").unwrap();
assert_eq!(ok.to_string(), "#103D18");
}
#[test]
fn test_hex_color_valid_lower_hex() {
let ok = HexColor::try_from("#103d18").unwrap();
assert_eq!(ok.to_string(), "#103d18");
}
}