Skip to main content

cloudreve_api/api/v3/
mod.rs

1//! API v3 implementation
2
3use crate::Error;
4use crate::api::v3::models::ApiResponse;
5use log::debug;
6use serde::Serialize;
7
8pub mod aria2;
9pub mod directory;
10pub mod file;
11pub mod models;
12pub mod object;
13pub mod session;
14pub mod share;
15pub mod site;
16pub mod user;
17
18/// API v3 client structure
19#[derive(Debug, Clone)]
20pub struct ApiV3Client {
21    pub base_url: String,
22    pub http_client: reqwest::Client,
23    pub session_cookie: Option<String>,
24}
25
26impl ApiV3Client {
27    pub fn new(base_url: &str) -> Self {
28        crate::ensure_tls_crypto_provider();
29
30        Self {
31            base_url: base_url.to_string(),
32            http_client: reqwest::Client::new(),
33            session_cookie: None,
34        }
35    }
36
37    pub fn set_session_cookie(&mut self, cookie: String) {
38        self.session_cookie = Some(cookie);
39    }
40
41    pub fn get_session_cookie(&self) -> Option<&str> {
42        self.session_cookie.as_deref()
43    }
44
45    pub fn clear_session_cookie(&mut self) {
46        self.session_cookie = None;
47    }
48
49    pub fn get_url(&self, endpoint: &str) -> String {
50        format!(
51            "{}/api/v3/{}",
52            self.base_url.trim_end_matches('/'),
53            endpoint.trim_start_matches('/')
54        )
55    }
56
57    pub async fn get<T>(&self, endpoint: &str) -> Result<T, Error>
58    where
59        T: serde::de::DeserializeOwned + std::fmt::Debug,
60    {
61        let url = self.get_url(endpoint);
62        let mut request = self.http_client.get(&url);
63
64        if let Some(cookie) = &self.session_cookie {
65            request = request.header("Cookie", format!("cloudreve-session={}", cookie));
66            debug!("cookie: {}", cookie);
67        }
68
69        debug!("GET URL: {}", url);
70
71        let response = request.send().await?;
72        let status = response.status();
73
74        // Check for error status codes first
75        if !status.is_success() {
76            let raw_text = response.text().await?;
77            if let Ok(api_response) =
78                serde_json::from_str::<ApiResponse<serde_json::Value>>(&raw_text)
79                && api_response.code != 0
80            {
81                return Err(Error::Api {
82                    code: api_response.code,
83                    message: api_response.msg,
84                });
85            }
86            return Err(Error::Api {
87                code: status.as_u16() as i32,
88                message: raw_text.trim().to_string(),
89            });
90        }
91
92        let raw_text = response.text().await?;
93
94        match serde_json::from_str::<T>(&raw_text) {
95            Ok(json) => {
96                debug!("Response status: {}, JSON: {:?}", status, json);
97                Ok(json)
98            }
99            Err(e) => {
100                debug!("JSON parse error: {}, raw response: {}", e, raw_text);
101                Err(Error::Json(e))
102            }
103        }
104    }
105
106    pub async fn post<T>(&self, endpoint: &str, body: &impl Serialize) -> Result<T, Error>
107    where
108        T: serde::de::DeserializeOwned + std::fmt::Debug,
109    {
110        let url = self.get_url(endpoint);
111        let mut request = self.http_client.post(&url).json(body);
112
113        if let Some(cookie) = &self.session_cookie {
114            request = request.header("Cookie", format!("cloudreve-session={}", cookie));
115            debug!("cookie: {}", cookie);
116        }
117
118        debug!("POST URL: {}", url);
119
120        let response = request.send().await?;
121        let status = response.status();
122
123        // Check for error status codes first
124        if !status.is_success() {
125            let raw_text = response.text().await?;
126            // Try to parse as API error response
127            if let Ok(api_response) =
128                serde_json::from_str::<ApiResponse<serde_json::Value>>(&raw_text)
129                && api_response.code != 0
130            {
131                return Err(Error::Api {
132                    code: api_response.code,
133                    message: api_response.msg,
134                });
135            }
136            // If not a standard API response, return error with status code
137            return Err(Error::Api {
138                code: status.as_u16() as i32,
139                message: raw_text.trim().to_string(),
140            });
141        }
142
143        // Get raw response text for better error reporting
144        let raw_text = response.text().await?;
145
146        match serde_json::from_str::<T>(&raw_text) {
147            Ok(json) => {
148                debug!("Response status: {}, JSON: {:?}", status, json);
149                Ok(json)
150            }
151            Err(e) => {
152                debug!("JSON parse error: {}, raw response: {}", e, raw_text);
153                Err(Error::Json(e))
154            }
155        }
156    }
157
158    /// POST request that returns raw text instead of parsing JSON
159    pub async fn post_raw(&self, endpoint: &str, body: &impl Serialize) -> Result<String, Error> {
160        let url = self.get_url(endpoint);
161        let mut request = self.http_client.post(&url).json(body);
162
163        if let Some(cookie) = &self.session_cookie {
164            request = request.header("Cookie", format!("cloudreve-session={}", cookie));
165            debug!("cookie: {}", cookie);
166        }
167
168        debug!("POST RAW URL: {}", url);
169
170        let response = request.send().await?;
171        let status = response.status();
172
173        if !status.is_success() {
174            let raw_text = response.text().await?;
175            return Err(Error::Api {
176                code: status.as_u16() as i32,
177                message: raw_text.trim().to_string(),
178            });
179        }
180
181        let text = response.text().await?;
182        debug!("Response status: {}, Text: {:?}", status, text);
183        Ok(text)
184    }
185
186    pub async fn put<T>(&self, endpoint: &str, body: &impl Serialize) -> Result<T, Error>
187    where
188        T: serde::de::DeserializeOwned + std::fmt::Debug,
189    {
190        let url = self.get_url(endpoint);
191        let mut request = self.http_client.put(&url).json(body);
192
193        if let Some(cookie) = &self.session_cookie {
194            request = request.header("Cookie", format!("cloudreve-session={}", cookie));
195            debug!("cookie: {}", cookie);
196        }
197
198        debug!("PUT URL: {}", url);
199
200        let response = request.send().await?;
201        let status = response.status();
202        let json: T = response.json().await?;
203        debug!("Response status: {}, JSON: {:?}", status, json);
204        Ok(json)
205    }
206
207    pub async fn patch<T>(&self, endpoint: &str, body: &impl Serialize) -> Result<T, Error>
208    where
209        T: serde::de::DeserializeOwned + std::fmt::Debug,
210    {
211        let url = self.get_url(endpoint);
212        let mut request = self.http_client.patch(&url).json(body);
213
214        if let Some(cookie) = &self.session_cookie {
215            request = request.header("Cookie", format!("cloudreve-session={}", cookie));
216            debug!("cookie: {}", cookie);
217        }
218
219        debug!("PATCH URL: {}", url);
220
221        let response = request.send().await?;
222        let status = response.status();
223        let json: T = response.json().await?;
224        debug!("Response status: {}, JSON: {:?}", status, json);
225        Ok(json)
226    }
227
228    pub async fn delete<T>(&self, endpoint: &str) -> Result<T, Error>
229    where
230        T: serde::de::DeserializeOwned + std::fmt::Debug,
231    {
232        let url = self.get_url(endpoint);
233        let mut request = self.http_client.delete(&url);
234
235        if let Some(cookie) = &self.session_cookie {
236            request = request.header("Cookie", format!("cloudreve-session={}", cookie));
237            debug!("cookie: {}", cookie);
238        }
239
240        debug!("DELETE URL: {}", url);
241
242        let response = request.send().await?;
243        let status = response.status();
244        let json: T = response.json().await?;
245        debug!("Response status: {}, JSON: {:?}", status, json);
246        Ok(json)
247    }
248
249    pub async fn delete_with_body<T>(
250        &self,
251        endpoint: &str,
252        body: &impl Serialize,
253    ) -> Result<T, Error>
254    where
255        T: serde::de::DeserializeOwned + std::fmt::Debug,
256    {
257        let url = self.get_url(endpoint);
258        let mut request = self.http_client.delete(&url).json(body);
259
260        if let Some(cookie) = &self.session_cookie {
261            request = request.header("Cookie", format!("cloudreve-session={}", cookie));
262            debug!("cookie: {}", cookie);
263        }
264
265        debug!("DELETE WITH BODY URL: {}", url);
266
267        let response = request.send().await?;
268        let status = response.status();
269        let json: T = response.json().await?;
270        debug!("Response status: {}, JSON: {:?}", status, json);
271        Ok(json)
272    }
273
274    /// PUT request that returns raw text response instead of JSON
275    pub async fn put_text(&self, endpoint: &str, body: &impl Serialize) -> Result<String, Error> {
276        let url = self.get_url(endpoint);
277        let mut request = self.http_client.put(&url).json(body);
278
279        if let Some(cookie) = &self.session_cookie {
280            request = request.header("Cookie", format!("cloudreve-session={}", cookie));
281            debug!("cookie: {}", cookie);
282        }
283
284        debug!("PUT TEXT URL: {}", url);
285
286        let response = request.send().await?;
287        let status = response.status();
288        let text = response.text().await?;
289        debug!("Response status: {}, Text: {:?}", status, text);
290
291        if !status.is_success() {
292            return Err(Error::Api {
293                code: status.as_u16() as i32,
294                message: text,
295            });
296        }
297
298        Ok(text)
299    }
300}