1use serde_json::Value;
2
3#[derive(Debug, Clone)]
4pub struct Auth {
5 username: String,
6 api_key: String,
7}
8
9impl Auth {
10 pub fn username(&self) -> &str {
11 &self.username
12 }
13
14 pub fn api_key(&self) -> &str {
15 &self.api_key
16 }
17
18 pub fn from<I>(username: I, api_key: I) -> Self
19 where
20 I: Into<String>,
21 {
22 Auth {
23 username: username.into(),
24 api_key: api_key.into(),
25 }
26 }
27}
28
29pub trait HttpClient {
30 async fn get(url: String, auth: Auth, params: &[(String, String)]) -> anyhow::Result<String>;
31 async fn put(url: String, auth: Auth, body: Value) -> anyhow::Result<String>;
32 async fn post(url: String, auth: Auth, body: Value) -> anyhow::Result<String>;
33 async fn delete(url: String, auth: Auth, body: Value) -> anyhow::Result<String>;
34}
35
36#[derive(Debug)]
37pub struct Request {
38 host: String,
39 path: String,
40 username: String,
41 api_key: String,
42 query_params: Vec<(String, String)>,
43}
44
45#[derive(Debug)]
46pub struct RequestBuilder {
47 host: String,
48 path: String,
49 username: String,
50 api_key: String,
51 query_params: Vec<(String, String)>,
52}
53
54impl RequestBuilder {
55 pub fn host(mut self, host: &str) -> Self {
56 self.host = host.to_string();
57 self
58 }
59
60 pub fn path(mut self, path: &str) -> Self {
61 self.path = path.to_string();
62 self
63 }
64
65 pub fn username(mut self, username: &str) -> Self {
66 self.username = username.to_string();
67 self
68 }
69
70 pub fn api_key(mut self, api_key: &str) -> Self {
71 self.api_key = api_key.to_string();
72 self
73 }
74
75 pub fn params(mut self, params: &[(String, String)]) -> Self {
76 for param in params {
77 self.query_params.push(param.clone())
78 }
79 self
80 }
81
82 pub fn build(self) -> Request {
83 Request {
84 host: self.host,
85 path: self.path,
86 username: self.username,
87 api_key: self.api_key,
88 query_params: self.query_params,
89 }
90 }
91}
92
93#[allow(clippy::new_ret_no_self)]
94impl Request {
95 pub fn new() -> RequestBuilder {
96 RequestBuilder {
97 host: "".to_string(),
98 path: "".to_string(),
99 username: "".to_string(),
100 query_params: vec![],
101 api_key: "".to_string(),
102 }
103 }
104
105 pub async fn get(&self) -> anyhow::Result<String> {
106 let client = reqwest::Client::new();
107 let endpoint = format!("{}{}", &self.host, &self.path);
108 let url = reqwest::Url::parse_with_params(&endpoint, &self.query_params)?;
109 let data = client
110 .get(url)
111 .basic_auth(self.username.as_str(), Some(self.api_key.as_str()))
112 .query(&self.query_params)
113 .send()
114 .await?
115 .text()
116 .await?;
117
118 Ok(data)
119 }
120
121 pub async fn put(&self, body: Value) -> anyhow::Result<String> {
122 let client = reqwest::Client::new();
123 let mut endpoint: String = String::from(&self.host);
124
125 endpoint.push_str(&self.path);
126
127 let data = client
128 .put(endpoint)
129 .basic_auth(self.username.as_str(), Some(self.api_key.as_str()))
130 .query(&self.query_params)
131 .json(&body)
132 .send()
133 .await?
134 .text()
135 .await?;
136
137 Ok(data)
138 }
139
140 pub async fn post(&self, body: Value) -> anyhow::Result<String> {
141 let client = reqwest::Client::new();
142 let mut endpoint: String = String::from(&self.host);
143
144 endpoint.push_str(&self.path);
145
146 let data = client
147 .post(endpoint)
148 .basic_auth(self.username.as_str(), Some(self.api_key.as_str()))
149 .query(&self.query_params)
150 .json(&body)
151 .send()
152 .await?
153 .text()
154 .await?;
155 Ok(data)
156 }
157
158 pub async fn delete(&self) -> anyhow::Result<String> {
159 let client = reqwest::Client::new();
160 let mut endpoint: String = String::from(&self.host);
161
162 endpoint.push_str(&self.path);
163
164 let data = client
165 .delete(endpoint)
166 .basic_auth(self.username.as_str(), Some(self.api_key.as_str()))
167 .query(&self.query_params)
168 .send()
169 .await?
170 .text()
171 .await?;
172
173 Ok(data)
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180 use dotenv::dotenv;
181 use serde_json::json;
182
183 fn setup() -> Request {
184 dotenv().ok();
185 let api_key = std::env::var("CSCART_API_KEY").expect("No api key found");
186
187 let username = std::env::var("CSCART_USERNAME").expect("No username found");
188
189 let host = std::env::var("CSCART_HOST").expect("No host found");
190
191 Request::new()
192 .host(host.as_str())
193 .path("/api/2.0/categories")
194 .username(username.as_str())
195 .api_key(api_key.as_str())
196 .build()
197 }
198
199 #[tokio::test]
200 async fn it_makes_a_get_request() {
201 let client = setup();
202 let response = client.get().await;
203
204 match response {
205 Ok(_) => assert!(true),
206 Err(_) => assert!(false),
207 }
208 }
209
210 #[tokio::test]
211 async fn it_makes_a_put_request() {
212 let client = setup();
213 let body = json!({"data" : "testing"});
214 let response = client.put(body).await;
215
216 match response {
217 Ok(_) => assert!(true),
218 Err(_) => assert!(false),
219 }
220 }
221
222 #[tokio::test]
223 async fn it_makes_a_post_request() {
224 let client = setup();
225 let body = json!({"data" : "testing"});
226 let response = client.post(body).await;
227
228 match response {
229 Ok(_) => assert!(true),
230 Err(_) => assert!(false),
231 }
232 }
233
234 #[tokio::test]
235 async fn it_makes_a_delete_request() {
236 let client = setup();
237 let response = client.delete().await;
238
239 match response {
240 Ok(_) => assert!(true),
241 Err(_) => assert!(false),
242 }
243 }
244}