lospec_cli/
palette.rs

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        // HACK: declare `s` as mut so we can move the slice forwards
37        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}