1use gloo_net::http::Request;
4use serde::{de::DeserializeOwned, Serialize};
5use std::rc::Rc;
6
7use super::error::{ApiError, ApiErrorResponse, ApiResult};
8
9#[derive(Clone)]
11pub struct ApiClient {
12 base_url: Rc<str>,
13}
14
15impl Default for ApiClient {
16 fn default() -> Self {
17 Self::local()
18 }
19}
20
21impl ApiClient {
22 pub fn new(base_url: impl Into<String>) -> Self {
24 Self {
25 base_url: base_url.into().into(),
26 }
27 }
28
29 pub fn local() -> Self {
31 Self::new("/api")
32 }
33
34 pub fn with_host(host: &str, port: u16) -> Self {
36 Self::new(format!("http://{}:{}/api", host, port))
37 }
38
39 pub fn base_url(&self) -> &str {
41 &self.base_url
42 }
43
44 fn url(&self, path: &str) -> String {
46 if path.starts_with('/') {
47 format!("{}{}", self.base_url, path)
48 } else {
49 format!("{}/{}", self.base_url, path)
50 }
51 }
52
53 async fn handle_response<T: DeserializeOwned>(
55 response: gloo_net::http::Response,
56 ) -> ApiResult<T> {
57 let status = response.status();
58
59 if response.ok() {
60 response
61 .json()
62 .await
63 .map_err(|e| ApiError::Deserialization(e.to_string()))
64 } else {
65 let error = match response.json::<ApiErrorResponse>().await {
67 Ok(err_resp) => err_resp.into(),
68 Err(_) => ApiError::from_status(status, response.status_text()),
69 };
70 Err(error)
71 }
72 }
73
74 async fn handle_empty_response(response: gloo_net::http::Response) -> ApiResult<()> {
76 let status = response.status();
77
78 if response.ok() {
79 Ok(())
80 } else {
81 let error = match response.json::<ApiErrorResponse>().await {
82 Ok(err_resp) => err_resp.into(),
83 Err(_) => ApiError::from_status(status, response.status_text()),
84 };
85 Err(error)
86 }
87 }
88
89 pub async fn get<T: DeserializeOwned>(&self, path: &str) -> ApiResult<T> {
91 let url = self.url(path);
92
93 let response = Request::get(&url)
94 .send()
95 .await
96 .map_err(|e| ApiError::Network(e.to_string()))?;
97
98 Self::handle_response(response).await
99 }
100
101 pub async fn get_with_query<T: DeserializeOwned, Q: Serialize>(
103 &self,
104 path: &str,
105 query: &Q,
106 ) -> ApiResult<T> {
107 let query_string = serde_urlencoded::to_string(query)
108 .map_err(|e| ApiError::Serialization(e.to_string()))?;
109
110 let url = if query_string.is_empty() {
111 self.url(path)
112 } else {
113 format!("{}?{}", self.url(path), query_string)
114 };
115
116 let response = Request::get(&url)
117 .send()
118 .await
119 .map_err(|e| ApiError::Network(e.to_string()))?;
120
121 Self::handle_response(response).await
122 }
123
124 pub async fn post<T: DeserializeOwned, B: Serialize>(
126 &self,
127 path: &str,
128 body: &B,
129 ) -> ApiResult<T> {
130 let url = self.url(path);
131
132 let response = Request::post(&url)
133 .header("Content-Type", "application/json")
134 .json(body)
135 .map_err(|e| ApiError::Serialization(e.to_string()))?
136 .send()
137 .await
138 .map_err(|e| ApiError::Network(e.to_string()))?;
139
140 Self::handle_response(response).await
141 }
142
143 pub async fn post_no_response<B: Serialize>(&self, path: &str, body: &B) -> ApiResult<()> {
145 let url = self.url(path);
146
147 let response = Request::post(&url)
148 .header("Content-Type", "application/json")
149 .json(body)
150 .map_err(|e| ApiError::Serialization(e.to_string()))?
151 .send()
152 .await
153 .map_err(|e| ApiError::Network(e.to_string()))?;
154
155 Self::handle_empty_response(response).await
156 }
157
158 pub async fn put<T: DeserializeOwned, B: Serialize>(
160 &self,
161 path: &str,
162 body: &B,
163 ) -> ApiResult<T> {
164 let url = self.url(path);
165
166 let response = Request::put(&url)
167 .header("Content-Type", "application/json")
168 .json(body)
169 .map_err(|e| ApiError::Serialization(e.to_string()))?
170 .send()
171 .await
172 .map_err(|e| ApiError::Network(e.to_string()))?;
173
174 Self::handle_response(response).await
175 }
176
177 pub async fn patch<T: DeserializeOwned, B: Serialize>(
179 &self,
180 path: &str,
181 body: &B,
182 ) -> ApiResult<T> {
183 let url = self.url(path);
184
185 let response = Request::patch(&url)
186 .header("Content-Type", "application/json")
187 .json(body)
188 .map_err(|e| ApiError::Serialization(e.to_string()))?
189 .send()
190 .await
191 .map_err(|e| ApiError::Network(e.to_string()))?;
192
193 Self::handle_response(response).await
194 }
195
196 pub async fn delete(&self, path: &str) -> ApiResult<()> {
198 let url = self.url(path);
199
200 let response = Request::delete(&url)
201 .send()
202 .await
203 .map_err(|e| ApiError::Network(e.to_string()))?;
204
205 Self::handle_empty_response(response).await
206 }
207
208 pub async fn delete_with_response<T: DeserializeOwned>(&self, path: &str) -> ApiResult<T> {
210 let url = self.url(path);
211
212 let response = Request::delete(&url)
213 .send()
214 .await
215 .map_err(|e| ApiError::Network(e.to_string()))?;
216
217 Self::handle_response(response).await
218 }
219}