1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
use cssparser::{Color as CssColor, Parser, ParserInput, ToCss};
use std::{error::Error, fmt::Display, str::FromStr};

#[cfg(feature = "serde_de")]
use serde::{de, Deserialize, Deserializer, Serialize};

pub const DEFAULT_WHITE: &'static str = "#fff";
pub const DEFAULT_BLACK: &'static str = "#000";
pub const DEFAULT_BLUE: &'static str = "#0366d6";
pub const DEFAULT_GRAY: &'static str = "#f6f8fa";
pub const DEFAULT_GRAY_DARK: &'static str = "#24292e";

#[derive(Debug, Eq, PartialEq, Clone)]
#[cfg_attr(feature = "serde_de", derive(Serialize))]
pub struct Color(pub String);

impl FromStr for Color {
    type Err = Box<dyn Error>;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut input = ParserInput::new(s);
        let mut parser = Parser::new(&mut input);

        CssColor::parse(&mut parser)
            .or_else(|_| CssColor::parse_hash(s.as_bytes()))
            .map(|c| Color(c.to_css_string()))
            .map_err(|_| format!("Invalid css color: {}", s).into())
    }
}

impl Default for Color {
    fn default() -> Self {
        "#000".parse().unwrap()
    }
}

impl Display for Color {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl AsRef<str> for Color {
    fn as_ref(&self) -> &str {
        self.0.as_str()
    }
}

#[cfg(feature = "serde_de")]
impl<'de> Deserialize<'de> for Color {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        s.as_str().parse().map_err(de::Error::custom)
    }
}

#[cfg(test)]
mod test {
    use super::Color;
    use std::str::FromStr;

    #[test]
    fn get_color_pass() {
        let colors = vec!["red", "#ff0000", "ff0000", "rgb(255, 0, 0)", "rgba(255, 0, 0, 1)"];

        let expected = Color(String::from("rgb(255, 0, 0)"));

        for c in colors {
            let cx = Color::from_str(c);
            assert!(cx.is_ok(), "input = {}, received = {:?}", c, cx);

            let cx = cx.unwrap();

            assert_eq!(
                cx, expected,
                "input = {}, received = {:?}, expected = {:?}",
                c, cx, expected
            )
        }
    }
    #[test]
    fn get_color_fail() {
        let colors = vec![
            "2983492837498723",
            "mixed",
            "#gg0000",
            "gg0000",
            "rbx(adf, 0, 0)",
            "rgba(ee0, 0, 0, 1)",
        ];

        for c in colors {
            let cx = Color::from_str(c);

            assert!(cx.is_err(), "input = {}, received = {:?}", c, cx);
        }
    }
}