toggl_rs/client/
mod.rs

1use crate::error::{Result, TogglError};
2use owo_colors::OwoColorize;
3use reqwest::{blocking, Method, StatusCode, Url};
4use serde::{de::DeserializeOwned, Serialize};
5use std::fmt::Debug;
6
7use self::me::MeClient;
8
9pub mod me;
10
11pub const DEFAULT_BASE_URL: &str = "https://api.track.toggl.com/api/v9/";
12
13#[derive(Debug, Clone)]
14pub struct TogglClient {
15    base_url: Url,
16    client: blocking::Client,
17    api_token: String,
18}
19
20impl TogglClient {
21    pub fn new(api_token: String) -> Result<TogglClient> {
22        #[cfg(not(test))]
23        let base_url = "https://api.track.toggl.com/api/v9/".parse()?;
24
25        #[cfg(test)]
26        let base_url = mockito::server_url().parse()?;
27
28        let client = blocking::Client::new();
29
30        Ok(TogglClient {
31            base_url,
32            client,
33            api_token,
34        })
35    }
36
37    fn base_request(&self, method: Method, uri: &str) -> Result<blocking::RequestBuilder> {
38        let url = self.base_url.join(uri)?;
39
40        Ok(self
41            .client
42            .request(method, url)
43            .basic_auth(&self.api_token, Some("api_token")))
44    }
45
46    fn request<D: DeserializeOwned + Debug>(
47        &self,
48        debug: bool,
49        method: Method,
50        uri: &str,
51    ) -> Result<D> {
52        let request = self.base_request(method, uri)?;
53
54        if debug {
55            println!("{}", "Request:".bold().underline());
56            println!("{:?}", request);
57            println!();
58        }
59
60        let response = request.send()?;
61
62        self.response(debug, response)
63    }
64
65    #[allow(dead_code)]
66    fn empty_request(&self, debug: bool, method: Method, uri: &str) -> Result<()> {
67        let request = self.base_request(method, uri)?;
68
69        if debug {
70            println!("{}", "Request:".bold().underline());
71            println!("{:?}", request);
72            println!();
73        }
74
75        let response = request.send()?;
76
77        self.empty_response(response)
78    }
79
80    #[allow(dead_code)]
81    fn request_with_body<D: DeserializeOwned + Debug, S: Serialize + Debug>(
82        &self,
83        debug: bool,
84        method: Method,
85        uri: &str,
86        body: S,
87    ) -> Result<D> {
88        let request = self.base_request(method, uri)?.json(&body);
89
90        if debug {
91            println!("{}", "Request:".bold().underline());
92            println!("{:?}", request);
93            println!();
94            println!("{:?}", &body);
95            println!();
96        }
97
98        let response = request.send()?;
99
100        self.response(debug, response)
101    }
102
103    fn response<D: DeserializeOwned + Debug>(
104        &self,
105        debug: bool,
106        response: blocking::Response,
107    ) -> Result<D> {
108        if debug {
109            println!("{}", "Response:".bold().underline());
110            println!("{:?}", response);
111            println!();
112        }
113
114        match response.status() {
115            StatusCode::OK | StatusCode::CREATED if debug => match response.json() {
116                Ok(json) => {
117                    println!("{}", "Received JSON response:".bold().underline());
118                    println!("{:?}", json);
119                    println!();
120
121                    Ok(json)
122                }
123                Err(err) => Err(TogglError::LibraryError(format!(
124                    "Failed to deserialize JSON: {}",
125                    err
126                ))),
127            },
128            StatusCode::OK | StatusCode::CREATED => Ok(response.json()?),
129            status => match response.text() {
130                Ok(text) => Err(TogglError::LibraryError(format!("{} - {}", status, text))),
131                Err(_) => Err(TogglError::LibraryError(format!("{}", status))),
132            },
133        }
134    }
135
136    fn empty_response(&self, response: blocking::Response) -> Result<()> {
137        match response.status() {
138            StatusCode::OK | StatusCode::CREATED => Ok(()),
139            status => match response.text() {
140                Ok(text) => Err(TogglError::LibraryError(format!("{} - {}", status, text))),
141                Err(_) => Err(TogglError::LibraryError(format!("{}", status))),
142            },
143        }
144    }
145
146    pub fn me(&self) -> MeClient {
147        MeClient::new(self.clone())
148    }
149}