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
101
102
103
104
105
106
use colored::Colorize;
use thiserror::Error;

use crate::{cli::Sorting, palette::Palettes};

#[derive(Debug, Error)]
pub enum Error {
    #[error(transparent)]
    RequestError(#[from] reqwest::Error),

    #[error(transparent)]
    DeserializationError(#[from] serde_json::Error),
}

/// Color search filter.
#[derive(Clone)]
pub enum Filter {
    /// Palettes with any number of colors.
    Any,
    /// Palettes with N maximum colors.
    Max(u16),
    /// Palettes with N minimum colors.
    Min(u16),
    /// Palettes with exactly N colors.
    Exact(u16),
}

pub struct Search {
    /// Which page to show.
    pub page: u16,
    /// Search filter.
    pub filter: Filter,
    /// Search results sorting.
    pub sorting: Sorting,
    /// Search tag (empty if no tag is being searched).
    pub tag: String,
}

impl Search {
    pub fn new(page: Option<u16>, filter: Filter, sorting: Sorting, tag: Option<String>) -> Self {
        Self {
            page: page.unwrap_or(1),
            filter,
            sorting,
            tag: tag.unwrap_or("".to_string()),
        }
    }

    /// Generate query parameters for the request.
    fn to_query(self) -> Vec<(&'static str, String)> {
        let mut params = vec![
            ("page", format!("{}", self.page)),
            ("sortingType", self.sorting.to_string()),
            ("tag", self.tag),
        ];

        match self.filter {
            Filter::Any => params.push(("colorNumberFilterType", "any".to_string())),
            Filter::Max(n) => {
                params.push(("colorNumberFilterType", "max".to_string()));
                params.push(("colorNumber", format!("{}", n)));
            }
            Filter::Min(n) => {
                params.push(("colorNumberFilterType", "min".to_string()));
                params.push(("colorNumber", format!("{}", n)));
            }
            Filter::Exact(n) => {
                params.push(("colorNumberFilterType", "exact".to_string()));
                params.push(("colorNumber", format!("{}", n)));
            }
        }

        params
    }

    /// Execute the search request.
    pub async fn execute(self) -> Result<(), Error> {
        let client = reqwest::Client::new();

        let response = client
            .get("https://lospec.com/palette-list/load")
            .query(&self.to_query())
            .send()
            .await?;

        let json: Palettes =
            serde_json::from_slice(&response.bytes().await.map_err(Error::RequestError)?)?;

        for palette in &json.palettes {
            if let Some(user) = &palette.user {
                println!("{} ({}) by {}", palette.title, palette.slug, user.name);
            } else {
                println!("{}", palette.title);
            }

            for color in &palette.colors {
                let colored_string = "  ".on_truecolor(color.red, color.green, color.blue);
                print!("{}", colored_string);
            }
            println!();
            println!();
        }

        Ok(())
    }
}