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