napchart/
api.rs

1/*
2 * --------------------
3 * THIS FILE IS LICENSED UNDER MIT
4 * THE FOLLOWING MESSAGE IS NOT A LICENSE
5 *
6 * <barrow@tilde.team> wrote this file.
7 * by reading this text, you are reading "TRANS RIGHTS".
8 * this file and the content within it is the gay agenda.
9 * if we meet some day, and you think this stuff is worth it,
10 * you can buy me a beer, tea, or something stronger.
11 * -Ezra Barrow
12 * --------------------
13 */
14//TODO: More docs
15//! Api clients for napchart.com
16
17use crate::error::*;
18use crate::raw;
19use crate::{Napchart, RemoteNapchart};
20use std::convert::TryInto;
21
22/// Builder struct for adding submittable metadata to a &Napchart
23/// ```
24/// # use napchart::*;
25/// # use napchart::api::mock::BlockingClient;
26/// let client = BlockingClient::default();
27/// let chart = Napchart::default();
28/// let upload = chart.upload();
29/// assert!(client.create_snapshot(upload).is_ok());
30/// ```
31pub struct UploadBuilder<'c> {
32    chart: &'c Napchart,
33    title: Option<String>,
34    description: Option<String>,
35}
36impl<'c> UploadBuilder<'c> {
37    pub(crate) fn new(n: &'c Napchart) -> Self {
38        Self {
39            chart: n,
40            title: None,
41            description: None,
42        }
43    }
44    /// Builder function to set the title of a napchart on upload.
45    /// ```
46    /// # use napchart::*;
47    /// # use napchart::api::mock::BlockingClient;
48    /// let client = BlockingClient::default();
49    /// let chart = Napchart::default();
50    /// let upload = chart.upload().title("My Cool Chart");
51    /// assert!(client.create_snapshot(upload).is_ok());
52    /// ```
53    pub fn title<T: AsRef<str>>(self, title: T) -> Self {
54        assert!(title.as_ref().len() <= 100);
55        Self {
56            title: Some(title.as_ref().to_string()),
57            ..self
58        }
59    }
60    /// Builder function to set the description of a napchart on upload.
61    /// ```
62    /// # use napchart::*;
63    /// # use napchart::api::mock::BlockingClient;
64    /// let client = BlockingClient::default();
65    /// let chart = Napchart::default();
66    /// let upload = chart.upload().description("This is my super cool chart. Please appreciate it");
67    /// assert!(client.create_snapshot(upload).is_ok());
68    /// ```
69    pub fn description<T: AsRef<str>>(self, description: T) -> Self {
70        Self {
71            description: Some(description.as_ref().to_string()),
72            ..self
73        }
74    }
75    fn build(self) -> Result<raw::ChartUploadRequest> {
76        Ok(raw::ChartUploadRequest {
77            chart_data: self.chart.clone().try_into()?,
78            title: self.title,
79            description: self.description,
80        })
81    }
82}
83
84#[doc(hidden)]
85pub mod mock {
86    #[derive(Default)]
87    pub struct BlockingClient {}
88    impl BlockingClient {
89        #[allow(dead_code)]
90        pub fn create_snapshot(
91            &self,
92            payload: super::UploadBuilder,
93        ) -> Result<(), crate::ErrorKind> {
94            let _ = payload.build()?;
95            Ok(())
96        }
97    }
98}
99
100/// Blocking <https://napchart.com> API client.
101/// Uses reqwest::blocking::Client internally.
102#[derive(Default)]
103pub struct BlockingClient {
104    internal: reqwest::blocking::Client,
105}
106impl BlockingClient {
107    /// Uploads a napchart (prepared as an UploadBuilder) to <https://napchart.com> and returns a
108    /// RemoteNapchart
109    pub fn create_snapshot(&self, payload: UploadBuilder) -> Result<RemoteNapchart> {
110        self.internal
111            .post("https://api.napchart.com/v1/createSnapshot")
112            .json(&payload.build()?)
113            .send()?
114            .json::<raw::ChartCreationReturn>()?
115            .try_into()
116    }
117    /// Downloads a napchart with the given chartid from <https://napchart.com> and returns it as a
118    /// RemoteNapchart
119    pub fn get_chart<T: AsRef<str>>(&self, chartid: T) -> Result<RemoteNapchart> {
120        self.internal
121            .get(format!(
122                "https://api.napchart.com/v1/getChart/{}",
123                chartid.as_ref()
124            ))
125            .send()?
126            .json::<raw::ChartCreationReturn>()?
127            .try_into()
128    }
129    #[allow(dead_code)]
130    pub(crate) fn get_chart_raw<T: AsRef<str>>(
131        &self,
132        chartid: T,
133    ) -> Result<raw::ChartCreationReturn> {
134        Ok(self
135            .internal
136            .get(format!(
137                "https://api.napchart.com/v1/getChart/{}",
138                chartid.as_ref()
139            ))
140            .send()?
141            .json::<raw::ChartCreationReturn>()?)
142    }
143}
144
145/// Async <https://napchart.com> API client.
146/// Uses reqwest::Client internally.
147#[derive(Default)]
148pub struct AsyncClient {
149    internal: reqwest::Client,
150}
151impl AsyncClient {
152    /// Uploads a napchart (prepared as an UploadBuilder) to <https://napchart.com> and returns a
153    /// RemoteNapchart
154    pub async fn create_snapshot(&self, payload: UploadBuilder<'_>) -> Result<RemoteNapchart> {
155        self.internal
156            .post("https://api.napchart.com/v1/createSnapshot")
157            .json(&payload.build()?)
158            .send()
159            .await?
160            .json::<raw::ChartCreationReturn>()
161            .await?
162            .try_into()
163    }
164    /// Downloads a napchart with the given chartid from <https://napchart.com> and returns it as a
165    /// RemoteNapchart
166    pub async fn get_chart<T: AsRef<str>>(&self, chartid: T) -> Result<RemoteNapchart> {
167        self.internal
168            .get(format!(
169                "https://api.napchart.com/v1/getChart/{}",
170                chartid.as_ref()
171            ))
172            .send()
173            .await?
174            .json::<raw::ChartCreationReturn>()
175            .await?
176            .try_into()
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183    use tokio::task::spawn_blocking;
184    // #[test]
185    // fn get_chart_raw() {
186    //     let client = BlockingClient::default();
187    //     let ccr = client.get_chart_raw("3tbkt").unwrap();
188    //     println!("{:#?}", ccr);
189    //     panic!();
190    // }
191    // #[test]
192    // fn get_custom_colors_raw() {
193    //     let client = BlockingClient::default();
194    //     let ccr = client.get_chart_raw("oo81DYL84").unwrap();
195    //     println!("{:#?}", ccr);
196    //     // panic!();
197    // }
198    #[test]
199    fn get_chart() {
200        let client = BlockingClient::default();
201        let rchart = client.get_chart("jex3y").unwrap();
202        assert_eq!(rchart.chartid, "jex3y");
203        assert_eq!(rchart.title, Some("simple test chart".to_string()));
204        assert!(rchart.description.is_none());
205        assert_eq!(rchart.username, Some("barrow".to_string()));
206        assert!(!rchart.is_snapshot);
207        assert_eq!(
208            rchart.public_link,
209            Some("https://napchart.com/barrow/simple-test-chart-jex3y".to_string())
210        );
211        let chart = rchart.chart;
212        assert_eq!(chart.shape, crate::ChartShape::Circle);
213        assert!(chart.color_tags.is_empty());
214        assert_eq!(chart.lanes.len(), 1);
215        let lane = chart.lanes.get(0).unwrap();
216        assert!(!lane.locked);
217        assert_eq!(lane.elements.len(), 2);
218    }
219    #[test]
220    fn get_custom_colors() {
221        let client = BlockingClient::default();
222        let rchart = client.get_chart("oo81DYL84").unwrap();
223        // let chart = client.get_chart_raw("oo81DYL84").unwrap();
224        // println!("{:#?}", chart);
225        // let rchart: RemoteNapchart = chart.try_into().unwrap();
226        let chart = rchart.chart;
227        assert_eq!(chart.color_tags.len(), 12);
228        let custom_0 = chart.color_tags.get(&crate::ChartColor::Custom0).unwrap();
229        println!("{:#?}", chart);
230        assert_eq!(custom_0, "custom_0 tag");
231        assert_eq!(
232            chart.custom_colors[0],
233            Some(colorsys::Rgb::from((0x50, 0x81, 0x4a)))
234        );
235    }
236    #[test]
237    fn create_snapshot() {
238        let client = BlockingClient::default();
239        let mut lchart = Napchart::default().shape(crate::ChartShape::Circle);
240        lchart
241            .add_lane()
242            .add_element(0, 8 * 60)
243            .unwrap()
244            .color(crate::ChartColor::Red);
245        lchart
246            .add_lane()
247            .add_element(8 * 60, 16 * 60)
248            .unwrap()
249            .color(crate::ChartColor::Blue);
250        let rchart = client
251            .create_snapshot(lchart.upload().title("napchart simple test chart"))
252            .unwrap();
253        assert_eq!(rchart.title, Some("napchart simple test chart".to_string()));
254        assert!(rchart.description.is_none());
255        assert_eq!(rchart.username, None);
256        assert!(rchart.is_snapshot);
257        assert_eq!(rchart.chart, lchart);
258        let chart = rchart.chart;
259        assert_eq!(chart.shape, crate::ChartShape::Circle);
260        assert!(chart.color_tags.is_empty());
261        assert_eq!(chart.lanes.len(), 2);
262        let lane1 = chart.lanes.get(0).unwrap();
263        let lane2 = chart.lanes.get(1).unwrap();
264        assert!(!lane1.locked);
265        assert!(!lane2.locked);
266        assert_eq!(lane1.elements.len(), 1);
267        assert_eq!(lane2.elements.len(), 1);
268        let elem1 = lane1.elems_iter().next().unwrap();
269        let elem2 = lane2.elems_iter().next().unwrap();
270        assert_eq!(elem1.start, 0);
271        assert_eq!(elem1.end, 8 * 60);
272        assert_eq!(elem2.start, 8 * 60);
273        assert_eq!(elem2.end, 16 * 60);
274        let elemd1 = &elem1.data;
275        let elemd2 = &elem2.data;
276        assert_eq!(elemd1.text, String::new());
277        assert_eq!(elemd2.text, String::new());
278        assert_eq!(elemd1.color, crate::ChartColor::Red);
279        assert_eq!(elemd2.color, crate::ChartColor::Blue);
280    }
281    #[tokio::test]
282    async fn get_chart_eq() {
283        let bres = spawn_blocking(move || {
284            let bclient = BlockingClient::default();
285            bclient.get_chart("bwul9").unwrap()
286        })
287        .await
288        .unwrap();
289        let aclient = AsyncClient::default();
290        let ares = aclient.get_chart("bwul9").await.unwrap();
291        assert!(ares.semantic_eq(&bres));
292    }
293    #[tokio::test]
294    async fn create_snapshot_eq() {
295        let mut achart = Napchart::default();
296        let lane = achart.add_lane();
297        lane.add_element(1, 72).unwrap();
298        lane.add_element(470, 472).unwrap();
299        lane.add_element(870, 873).unwrap();
300        lane.add_element(1270, 1274).unwrap();
301        let bchart = achart.clone();
302        let aup = achart
303            .upload()
304            .title("create_snapshot equivalence test")
305            .description("");
306        let (brchart1, brchart2) = spawn_blocking(move || {
307            let bup = bchart
308                .upload()
309                .title("create_snapshot equivalence test")
310                .description("");
311            let bclient = BlockingClient::default();
312            let brchart1 = bclient.create_snapshot(bup).unwrap();
313            let brchart2 = bclient.get_chart(&brchart1.chartid).unwrap();
314            (brchart1, brchart2)
315        })
316        .await
317        .unwrap();
318        let aclient = AsyncClient::default();
319        let archart1 = aclient.create_snapshot(aup).await.unwrap();
320        let archart2 = aclient.get_chart(&archart1.chartid).await.unwrap();
321        assert!(archart1.semantic_eq(&brchart1));
322        assert!(archart2.semantic_eq(&brchart2));
323        // assert_eq!(ares, bres);
324    }
325}