twilight_model/util/
hex_color.rs1use std::fmt::Formatter;
2use std::fmt::{Display, Result as FmtResult};
3use std::num::ParseIntError;
4use std::str::FromStr;
5
6use serde::de::Visitor;
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8
9#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
11pub struct HexColor(
12 pub u8,
14 pub u8,
16 pub u8,
18);
19
20impl Display for HexColor {
21 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
22 f.write_fmt(format_args!("#{:02X}{:02X}{:02X}", self.0, self.1, self.2))
23 }
24}
25
26impl Serialize for HexColor {
27 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
28 serializer.serialize_str(&self.to_string())
29 }
30}
31
32#[derive(Debug)]
33pub enum HexColorParseError {
34 InvalidLength,
35 InvalidFormat,
36 InvalidCharacter(ParseIntError),
37}
38
39impl From<ParseIntError> for HexColorParseError {
40 fn from(err: ParseIntError) -> Self {
41 Self::InvalidCharacter(err)
42 }
43}
44
45impl FromStr for HexColor {
46 type Err = HexColorParseError;
47
48 fn from_str(s: &str) -> Result<Self, Self::Err> {
49 if !s.starts_with('#') {
50 return Err(HexColorParseError::InvalidFormat);
51 }
52
53 let s = s.trim_start_matches('#');
54
55 let (r, g, b) = match s.len() {
56 3 => (
57 u8::from_str_radix(&s[0..1], 16)?,
58 u8::from_str_radix(&s[1..2], 16)?,
59 u8::from_str_radix(&s[2..3], 16)?,
60 ),
61 6 => (
62 u8::from_str_radix(&s[0..2], 16)?,
63 u8::from_str_radix(&s[2..4], 16)?,
64 u8::from_str_radix(&s[4..6], 16)?,
65 ),
66 _ => return Err(HexColorParseError::InvalidLength),
67 };
68
69 Ok(Self(r, g, b))
70 }
71}
72
73struct HexColorVisitor;
74
75impl Visitor<'_> for HexColorVisitor {
76 type Value = HexColor;
77
78 fn expecting(&self, formatter: &mut Formatter) -> FmtResult {
79 formatter.write_str("a hex color string")
80 }
81
82 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
83 where
84 E: serde::de::Error,
85 {
86 HexColor::from_str(v).map_err(|_| E::custom("invalid hex color"))
87 }
88}
89
90impl<'de> Deserialize<'de> for HexColor {
91 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
92 deserializer.deserialize_str(HexColorVisitor)
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::HexColor;
99
100 #[test]
101 fn hex_color_display() {
102 let hex_color = HexColor(255, 255, 255);
103 assert_eq!(hex_color.to_string(), "#FFFFFF");
104 }
105
106 #[test]
107 fn serialize() {
108 let hex_color = HexColor(252, 177, 3);
109 let serialized = serde_json::to_string(&hex_color).unwrap();
110 assert_eq!(serialized, "\"#FCB103\"");
111 }
112
113 #[test]
114 fn serialize_2() {
115 let hex_color = HexColor(255, 255, 255);
116 let serialized = serde_json::to_string(&hex_color).unwrap();
117 assert_eq!(serialized, "\"#FFFFFF\"");
118 }
119
120 #[test]
121 fn deserialize() {
122 let deserialized: HexColor = serde_json::from_str("\"#FFFFFF\"").unwrap();
123 assert_eq!(deserialized, HexColor(255, 255, 255));
124 }
125
126 #[test]
127 fn deserialize_invalid() {
128 let deserialized: Result<HexColor, _> = serde_json::from_str("\"#GGGGGG\"");
129 assert!(deserialized.is_err());
130 }
131}