1use std::str::FromStr;
2
3use serde::{de::Visitor, Deserialize};
4use thiserror::Error;
5
6type UtcDateTime = chrono::DateTime<chrono::Utc>;
7
8#[inline(always)]
9fn err_to_serde_err<E, SE>(err: E) -> SE
10where
11 E: std::error::Error,
12 SE: serde::de::Error,
13{
14 serde::de::Error::custom(err.to_string())
15}
16
17#[derive(Debug, Error)]
18pub enum ParseColorError {
19 #[error("unable to parse hex value")]
20 ParseHexError,
21 #[error(transparent)]
22 ParseIntError(std::num::ParseIntError),
23}
24
25#[derive(Debug)]
26pub struct Color {
27 pub red: u8,
28 pub green: u8,
29 pub blue: u8,
30}
31
32impl FromStr for Color {
33 type Err = ParseColorError;
34
35 fn from_str(s: &str) -> Result<Self, Self::Err> {
36 let mut s = s;
38 if s.starts_with("0x") {
39 s = &s[2..];
40 }
41 if s.len() < 6 {
42 return Err(ParseColorError::ParseHexError);
43 }
44 let red = u8::from_str_radix(&s[0..2], 16).map_err(ParseColorError::ParseIntError)?;
45 let green = u8::from_str_radix(&s[2..4], 16).map_err(ParseColorError::ParseIntError)?;
46 let blue = u8::from_str_radix(&s[4..6], 16).map_err(ParseColorError::ParseIntError)?;
47 Ok(Color { red, green, blue })
48 }
49}
50
51impl<'de> Deserialize<'de> for Color {
52 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
53 where
54 D: serde::Deserializer<'de>,
55 {
56 struct FieldVisitor;
57
58 impl<'de> Visitor<'de> for FieldVisitor {
59 type Value = Color;
60
61 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
62 formatter.write_str("hex value")
63 }
64
65 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
66 where
67 E: serde::de::Error,
68 {
69 Color::from_str(v).map_err(err_to_serde_err)
70 }
71
72 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
73 where
74 E: serde::de::Error,
75 {
76 self.visit_str(&v)
77 }
78 }
79
80 deserializer.deserialize_str(FieldVisitor)
81 }
82}
83
84#[derive(Debug, Deserialize)]
85pub struct User {
86 pub name: String,
87 pub slug: String,
88}
89
90#[derive(Debug, Deserialize)]
91#[serde(rename_all(deserialize = "camelCase"))]
92pub struct Palette {
93 #[serde(rename(deserialize = "_id"))]
94 pub id: String,
95 pub tags: Vec<String>,
96 pub colors: Vec<Color>,
97 pub title: String,
98 pub slug: String,
99 pub published_at: UtcDateTime,
100 pub user: Option<User>,
101 pub created_at: UtcDateTime,
102}
103
104#[derive(Debug, Deserialize)]
105pub struct Palettes {
106 pub palettes: Vec<Palette>,
107}