firefly_iii_rust/
client.rs

1use crate::requests::*;
2use crate::response::*;
3use crate::error::Result;
4use crate::error::Error;
5use crate::http::Method;
6use serde::de::DeserializeOwned;
7
8/// The main entry point into this crate. Hold the access token
9/// currently `base_url` needs to be post fixed with `/api` for
10/// this library to actually work.
11/// TODO: that should be fixed.
12#[derive(Debug)]
13pub struct Client {
14    base_url: String,
15    token: String,
16    client: ureq::Agent,
17}
18
19pub fn new(base_url: &str, token: &str) -> Client {
20    let client = ureq::Agent::new();
21    Client{base_url: base_url.into(), token: token.into(), client}
22}
23
24impl Client {
25    /// This is our generic calling function for the API, the error
26    /// handling is somewhat ass since it doesn't use https://docs.rs/ureq/latest/ureq/enum.Error.html#examples
27    /// because the error enum in this repository somehow caused an
28    /// inifinite loop with the transformations.
29    ///
30    /// TODO: The error should properly parse the actually useful error
31    /// responses from the API.
32    pub fn call<R: Request>(&self, request: R) -> Result<R::Response> {
33        let url = format!("{}{}", self.base_url, request.endpoint());
34        let mut req = match R::METHOD {
35            Method::Head => self.client.head(&url),
36            Method::Get => self.client.get(&url),
37            Method::Post => self.client.post(&url),
38            Method::Put => self.client.put(&url),
39            Method::Delete => self.client.delete(&url),
40        };
41
42        req = req.set("Authorization", &format!("Bearer {}", self.token));
43        req = req.set("accept", "application/vnd.api+json");
44        req = req.set("Content-Type", "application/json");
45
46        for (k, v) in request.headers().iter() {
47            req = req.set(k,v);
48        }
49
50        let resp = match request.body() {
51            Some(body) => {
52                req.send_json(body)
53            },
54            None => req.call(),
55        };
56
57        match resp {
58            Ok(resp) => {
59                if resp.status() == 204 {
60                    Ok(R::Response::default()) 
61                } else {
62                    let result: R::Response = resp.into_json()
63                       .expect("json conversion failed on us");
64                    Ok(result)
65                }
66            },
67            Err(err) => {
68                if let Some(body) = request.body() {
69                    eprintln!("Body: {:#?}", body);
70                }
71                match err {
72                    ureq::Error::Status(status, response) => {
73                        let response_msg = response.into_string().unwrap_or_else(|_| "failed to parse response".to_string());
74                        Err(Error::HTTPErr{status, response_msg: response_msg.to_string()})
75                    },
76                    _ => Err(Error::IOErr(err.to_string())),
77                }
78            },
79        }
80    }
81
82    /// Consumes all pages of the API in sequence and assembles a flat Vec of
83    /// all pages.
84    pub fn fetch_all<R, T>(&self, mut request: R) -> Result<Vec<T>>
85    where
86        R: Request<Response = PaginatedResponse<Vec<T>>> + Paginated + Clone,
87        T: DeserializeOwned,
88    {
89        let mut current_page = 1;
90        let mut all_items = Vec::new();
91
92        loop {
93            request.set_page(current_page);
94            let response = self.call(request.clone())?;
95            all_items.extend(response.data.into_iter());
96
97            if request.get_page() < request.max_page() {
98                current_page += 1;
99            } else {
100                break; // we're done
101            }
102        }
103
104        Ok(all_items)
105    }
106}
107