top_gg/
widget.rs

1//! Types for generating widget embed URLs.
2
3use crate::{
4    endpoints,
5    error::{InvalidUrl, Result},
6};
7use snafu::ResultExt;
8use std::collections::HashMap;
9use url::Url;
10
11#[derive(Clone, Debug)]
12struct Widget(u64, HashMap<&'static str, String>, bool);
13
14impl Widget {
15    fn build(self) -> Result<String> {
16        let widget_uri = if self.2 {
17            endpoints::png_widget(self.0)
18        } else {
19            endpoints::widget(self.0)
20        };
21
22        let params = self.1.into_iter().map(|(k, v)| (k, v)).collect::<Vec<_>>();
23
24        let url = Url::parse_with_params(&widget_uri, params)
25            .with_context(|| InvalidUrl { uri: widget_uri })?;
26
27        Ok(url.into_string())
28    }
29
30    fn insert(&mut self, k: &'static str, v: impl Into<String>) -> &mut Self {
31        self._insert(k, v.into())
32    }
33
34    fn _insert(&mut self, k: &'static str, v: String) -> &mut Self {
35        self.1.insert(k, v);
36
37        self
38    }
39}
40
41/// Type-safe and guarenteed method of making a large widget.
42#[derive(Clone, Debug)]
43pub struct LargeWidget(Widget);
44
45impl LargeWidget {
46    /// Creates a new builder for making a large widget.
47    pub fn new(bot_id: u64) -> Self {
48        Self(Widget(bot_id, HashMap::new(), false))
49    }
50
51    /// Builds into a valid URL.
52    ///
53    /// # Errors
54    ///
55    /// Returns [`Error::InvalidUrl`] if one of the query parameters is invalid.
56    ///
57    /// [`Error::InvalidUrl`]: ../enum.Error.html#variant.InvalidUrl
58    pub fn build(self) -> Result<String> {
59        self.0.build()
60    }
61
62    /// Sets the top color of the widget.
63    pub fn top_color(&mut self, value: impl Into<String>) -> &mut Self {
64        self.0.insert("topcolor", value);
65
66        self
67    }
68
69    /// Sets the middle color of the widget.
70    pub fn middle_color(&mut self, value: impl Into<String>) -> &mut Self {
71        self.0.insert("middlecolor", value);
72
73        self
74    }
75
76    /// Sets the username color of the widget.
77    pub fn username_color(&mut self, value: impl Into<String>) -> &mut Self {
78        self.0.insert("usernamecolor", value);
79
80        self
81    }
82
83    /// Sets the certified color of the widget.
84    pub fn certified_color(&mut self, value: impl Into<String>) -> &mut Self {
85        self.0.insert("certifiedcolor", value);
86
87        self
88    }
89
90    /// Sets the data color of the widget.
91    pub fn data_color(&mut self, value: impl Into<String>) -> &mut Self {
92        self.0.insert("datacolor", value);
93
94        self
95    }
96
97    /// Sets the label color of the widget.
98    pub fn label_color(&mut self, value: impl Into<String>) -> &mut Self {
99        self.0.insert("labelcolor", value);
100
101        self
102    }
103
104    /// Sets if the widget should be a png instead of a svg.
105    pub fn png(&mut self, value: bool) -> &mut Self {
106        (self.0).2 = value;
107
108        self
109    }
110}
111
112/// Type-safe and guarenteed method of making a small widget.
113#[derive(Clone, Debug)]
114pub struct SmallWidget(Widget);
115
116impl SmallWidget {
117    /// Creates a new builder for making a small widget.
118    pub fn new(bot_id: u64) -> Self {
119        Self(Widget(bot_id, HashMap::new(), false))
120    }
121
122    /// Builds into a valid URL.
123    ///
124    /// # Errors
125    ///
126    /// Returns [`Error::InvalidUrl`] if one of the query parameters is invalid.
127    ///
128    /// [`Error::InvalidUrl`]: ../enum.Error.html#variant.InvalidUrl
129    pub fn build(self) -> Result<String> {
130        self.0.build()
131    }
132
133    /// Sets the background color of the widget.
134    pub fn avatar_background(&mut self, value: impl Into<String>) -> &mut Self {
135        self.0.insert("avatarbg", value);
136
137        self
138    }
139
140    /// Sets the left color of the widget.
141    pub fn left_color(&mut self, value: impl Into<String>) -> &mut Self {
142        self.0.insert("leftcolor", value);
143
144        self
145    }
146
147    /// Sets the left text color of the widget.
148    pub fn left_text_color(&mut self, value: impl Into<String>) -> &mut Self {
149        self.0.insert("lefttextcolor", value);
150
151        self
152    }
153
154    /// Sets the right color of the widget.
155    pub fn right_color(&mut self, value: impl Into<String>) -> &mut Self {
156        self.0.insert("rightcolor", value);
157
158        self
159    }
160
161    /// Sets the right text color of the widget.
162    pub fn right_text_color(&mut self, value: impl Into<String>) -> &mut Self {
163        self.0.insert("righttextcolor", value);
164
165        self
166    }
167
168    /// Sets if the widget should be a png instead of a svg.
169    pub fn png(&mut self, value: bool) -> &mut Self {
170        (self.0).2 = value;
171
172        self
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::{LargeWidget, SmallWidget};
179    use crate::Result;
180
181    // The ordering of `url`'s parsed query parameters isn't always
182    // reproducable.
183    #[test]
184    fn test_small_widget() -> Result<()> {
185        let mut widget = SmallWidget::new(1);
186        widget
187            .avatar_background("00FF00")
188            .left_color("FF0000")
189            .left_text_color("FFFFFF")
190            .right_color("0F0F0F")
191            .right_text_color("F0F0F0")
192            .png(true);
193
194        let url = widget.build()?;
195        assert!(url.contains("avatarbg=00FF00"));
196        assert!(url.contains("lefttextcolor=FFFFFF"));
197        assert!(url.contains("leftcolor=FF0000"));
198        assert!(url.contains("rightcolor=0F0F0F"));
199        assert!(url.contains("righttextcolor=F0F0F0"));
200        assert!(url.starts_with("https://top.gg/api/widget/1.png?"));
201
202        Ok(())
203    }
204
205    #[test]
206    fn test_large_widget() -> Result<()> {
207        let mut widget = LargeWidget::new(1);
208        widget
209            .certified_color("FF0000")
210            .data_color("00FF00")
211            .label_color("0000FF")
212            .middle_color("FFF000")
213            .top_color("000FFF")
214            .username_color("AAAAAA");
215
216        let url = widget.build()?;
217        assert!(url.contains("certifiedcolor=FF0000"));
218        assert!(url.contains("datacolor=00FF00"));
219        assert!(url.contains("labelcolor=0000FF"));
220        assert!(url.contains("middlecolor=FFF000"));
221        assert!(url.contains("topcolor=000FFF"));
222        assert!(url.contains("usernamecolor=AAAAAA"));
223        assert!(url.starts_with("https://top.gg/api/widget/1.svg?"));
224
225        Ok(())
226    }
227}