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}