Skip to main content

cloudreve_api/api/v4/
mod.rs

1//! API v4 implementation
2
3use crate::Error;
4use log::debug;
5use serde::Serialize;
6
7/// WebDAV account management methods for v4 API
8pub mod dav;
9/// File management methods for v4 API
10pub mod file;
11/// Common data models for v4 API
12pub mod models;
13/// Session management methods for v4 API
14pub mod session;
15/// Share management methods for v4 API
16pub mod share;
17/// Site-related methods for v4 API
18pub mod site;
19/// URI handling utilities for v4 API
20pub mod uri;
21/// User management methods for v4 API
22pub mod user;
23/// Workflow management methods for v4 API
24pub mod workflow;
25
26/// API v4 client structure
27#[derive(Debug, Clone)]
28pub struct ApiV4Client {
29    /// Base URL for the Cloudreve instance
30    pub base_url: String,
31    /// HTTP client for making requests
32    pub http_client: reqwest::Client,
33    /// Authentication token (access token)
34    pub token: Option<String>,
35    /// Refresh token for obtaining new access tokens
36    pub refresh_token: Option<String>,
37}
38
39impl ApiV4Client {
40    /// Creates a new API v4 client
41    pub fn new(base_url: &str) -> Self {
42        crate::ensure_tls_crypto_provider();
43
44        Self {
45            base_url: base_url.to_string(),
46            http_client: reqwest::Client::new(),
47            token: None,
48            refresh_token: None,
49        }
50    }
51
52    /// Sets the authentication token
53    pub fn set_token(&mut self, token: String) {
54        self.token = Some(token);
55    }
56
57    /// Clears the authentication tokens
58    pub fn clear_token(&mut self) {
59        self.token = None;
60        self.refresh_token = None;
61    }
62
63    /// Sets the refresh token
64    pub fn set_refresh_token(&mut self, token: String) {
65        self.refresh_token = Some(token);
66    }
67
68    /// Gets the refresh token
69    pub fn get_refresh_token(&self) -> Option<&str> {
70        self.refresh_token.as_deref()
71    }
72
73    /// Gets the full URL for an endpoint with /api/v4 prefix
74    fn get_url(&self, endpoint: &str) -> String {
75        format!(
76            "{}/api/v4/{}",
77            self.base_url.trim_end_matches('/'),
78            endpoint.trim_start_matches('/')
79        )
80    }
81
82    /// Makes a GET request to the API
83    pub async fn get<T>(&self, endpoint: &str) -> Result<T, Error>
84    where
85        T: serde::de::DeserializeOwned + std::fmt::Debug,
86    {
87        let url = self.get_url(endpoint);
88        let mut request = self.http_client.get(&url);
89
90        if let Some(token) = &self.token {
91            request = request.bearer_auth(token);
92        }
93        debug!("GET URL: {}", url);
94
95        let response = request.send().await?;
96        let status = response.status();
97
98        // Check for error status codes first
99        if !status.is_success() {
100            let raw_text = response.text().await?;
101            // Try to parse as API error response
102            if let Ok(api_response) =
103                serde_json::from_str::<crate::ApiResponse<serde_json::Value>>(&raw_text)
104                && api_response.code != 0
105            {
106                return Err(Error::Api {
107                    code: api_response.code,
108                    message: api_response.msg,
109                });
110            }
111            // If not a standard API response, return error with status code
112            return Err(Error::Api {
113                code: status.as_u16() as i32,
114                message: raw_text.trim().to_string(),
115            });
116        }
117
118        // Get raw response text for better error reporting
119        let raw_text = response.text().await?;
120
121        // Check application-level error codes before attempting full parse
122        if let Ok(base) = serde_json::from_str::<crate::ApiResponse<serde_json::Value>>(&raw_text)
123            && base.code == 401
124        {
125            return Err(Error::Unauthorized(base.msg));
126        }
127
128        match serde_json::from_str::<T>(&raw_text) {
129            Ok(json) => {
130                debug!("Response status: {}, JSON: {:?}", status, json);
131                Ok(json)
132            }
133            Err(e) => {
134                debug!("JSON parse error: {}, raw response: {}", e, raw_text);
135                Err(Error::Json(e))
136            }
137        }
138    }
139
140    /// Makes a POST request to the API
141    pub async fn post<T>(&self, endpoint: &str, body: &impl Serialize) -> Result<T, Error>
142    where
143        T: serde::de::DeserializeOwned + std::fmt::Debug,
144    {
145        let url = self.get_url(endpoint);
146        let mut request = self.http_client.post(&url).json(body);
147
148        if let Some(token) = &self.token {
149            request = request.bearer_auth(token);
150        }
151
152        debug!("POST URL: {}", url);
153
154        // Log the request body for debugging
155        if let Ok(json_str) = serde_json::to_string(body) {
156            debug!("Request body: {}", json_str);
157        }
158
159        let response = request.send().await?;
160        let status = response.status();
161
162        // Check for error status codes first
163        if !status.is_success() {
164            let raw_text = response.text().await?;
165            if let Ok(api_response) =
166                serde_json::from_str::<crate::ApiResponse<serde_json::Value>>(&raw_text)
167                && api_response.code != 0
168            {
169                return Err(Error::Api {
170                    code: api_response.code,
171                    message: api_response.msg,
172                });
173            }
174            return Err(Error::Api {
175                code: status.as_u16() as i32,
176                message: raw_text.trim().to_string(),
177            });
178        }
179
180        let raw_text = response.text().await?;
181
182        if let Ok(base) = serde_json::from_str::<crate::ApiResponse<serde_json::Value>>(&raw_text)
183            && base.code == 401
184        {
185            return Err(Error::Unauthorized(base.msg));
186        }
187
188        match serde_json::from_str::<T>(&raw_text) {
189            Ok(json) => {
190                debug!("Response status: {}, JSON: {:?}", status, json);
191                Ok(json)
192            }
193            Err(e) => {
194                debug!("JSON parse error: {}, raw response: {}", e, raw_text);
195                Err(Error::Json(e))
196            }
197        }
198    }
199
200    /// Makes a PUT request to the API
201    pub async fn put<T>(&self, endpoint: &str, body: &impl Serialize) -> Result<T, Error>
202    where
203        T: serde::de::DeserializeOwned + std::fmt::Debug,
204    {
205        let url = self.get_url(endpoint);
206        let mut request = self.http_client.put(&url).json(body);
207
208        if let Some(token) = &self.token {
209            request = request.bearer_auth(token);
210        }
211        debug!("PUT URL: {}", url);
212
213        let response = request.send().await?;
214        let status = response.status();
215
216        // Check for error status codes first
217        if !status.is_success() {
218            let raw_text = response.text().await?;
219            if let Ok(api_response) =
220                serde_json::from_str::<crate::ApiResponse<serde_json::Value>>(&raw_text)
221                && api_response.code != 0
222            {
223                return Err(Error::Api {
224                    code: api_response.code,
225                    message: api_response.msg,
226                });
227            }
228            return Err(Error::Api {
229                code: status.as_u16() as i32,
230                message: raw_text.trim().to_string(),
231            });
232        }
233
234        let raw_text = response.text().await?;
235
236        match serde_json::from_str::<T>(&raw_text) {
237            Ok(json) => {
238                debug!("Response status: {}, JSON: {:?}", status, json);
239                Ok(json)
240            }
241            Err(e) => {
242                debug!("JSON parse error: {}, raw response: {}", e, raw_text);
243                Err(Error::Json(e))
244            }
245        }
246    }
247
248    pub async fn patch<T>(&self, endpoint: &str, body: &impl Serialize) -> Result<T, Error>
249    where
250        T: serde::de::DeserializeOwned + std::fmt::Debug,
251    {
252        let url = self.get_url(endpoint);
253        let mut request = self.http_client.patch(&url).json(body);
254
255        if let Some(token) = &self.token {
256            request = request.bearer_auth(token);
257        }
258        debug!("PATCH URL: {}", url);
259
260        let response = request.send().await?;
261        let status = response.status();
262
263        // Check for error status codes first
264        if !status.is_success() {
265            let raw_text = response.text().await?;
266            if let Ok(api_response) =
267                serde_json::from_str::<crate::ApiResponse<serde_json::Value>>(&raw_text)
268                && api_response.code != 0
269            {
270                return Err(Error::Api {
271                    code: api_response.code,
272                    message: api_response.msg,
273                });
274            }
275            return Err(Error::Api {
276                code: status.as_u16() as i32,
277                message: raw_text.trim().to_string(),
278            });
279        }
280
281        let raw_text = response.text().await?;
282
283        match serde_json::from_str::<T>(&raw_text) {
284            Ok(json) => {
285                debug!("Response status: {}, JSON: {:?}", status, json);
286                Ok(json)
287            }
288            Err(e) => {
289                debug!("JSON parse error: {}, raw response: {}", e, raw_text);
290                Err(Error::Json(e))
291            }
292        }
293    }
294
295    /// Makes a DELETE request to the API
296    pub async fn delete<T>(&self, endpoint: &str) -> Result<T, Error>
297    where
298        T: serde::de::DeserializeOwned + std::fmt::Debug,
299    {
300        let url = self.get_url(endpoint);
301        let mut request = self.http_client.delete(&url);
302
303        if let Some(token) = &self.token {
304            request = request.bearer_auth(token);
305        }
306        debug!("DELETE URL: {}", url);
307
308        let response = request.send().await?;
309        let status = response.status();
310
311        // Check for error status codes first
312        if !status.is_success() {
313            let raw_text = response.text().await?;
314            if let Ok(api_response) =
315                serde_json::from_str::<crate::ApiResponse<serde_json::Value>>(&raw_text)
316                && api_response.code != 0
317            {
318                return Err(Error::Api {
319                    code: api_response.code,
320                    message: api_response.msg,
321                });
322            }
323            return Err(Error::Api {
324                code: status.as_u16() as i32,
325                message: raw_text.trim().to_string(),
326            });
327        }
328
329        let raw_text = response.text().await?;
330
331        if let Ok(base) = serde_json::from_str::<crate::ApiResponse<serde_json::Value>>(&raw_text)
332            && base.code == 401
333        {
334            return Err(Error::Unauthorized(base.msg));
335        }
336
337        match serde_json::from_str::<T>(&raw_text) {
338            Ok(json) => {
339                debug!("Response status: {}, JSON: {:?}", status, json);
340                Ok(json)
341            }
342            Err(e) => {
343                debug!("JSON parse error: {}, raw response: {}", e, raw_text);
344                Err(Error::Json(e))
345            }
346        }
347    }
348
349    /// Makes a DELETE request with JSON body to the API
350    pub async fn delete_with_body<T>(
351        &self,
352        endpoint: &str,
353        body: &impl Serialize,
354    ) -> Result<T, Error>
355    where
356        T: serde::de::DeserializeOwned + std::fmt::Debug,
357    {
358        let url = self.get_url(endpoint);
359        let mut request = self.http_client.delete(&url).json(body);
360
361        if let Some(token) = &self.token {
362            request = request.bearer_auth(token);
363        }
364        debug!("DELETE URL: {} with body", url);
365
366        let response = request.send().await?;
367        let status = response.status();
368
369        // Check for error status codes first
370        if !status.is_success() {
371            let raw_text = response.text().await?;
372            if let Ok(api_response) =
373                serde_json::from_str::<crate::ApiResponse<serde_json::Value>>(&raw_text)
374                && api_response.code != 0
375            {
376                return Err(Error::Api {
377                    code: api_response.code,
378                    message: api_response.msg,
379                });
380            }
381            return Err(Error::Api {
382                code: status.as_u16() as i32,
383                message: raw_text.trim().to_string(),
384            });
385        }
386
387        let raw_text = response.text().await?;
388
389        if let Ok(base) = serde_json::from_str::<crate::ApiResponse<serde_json::Value>>(&raw_text)
390            && base.code == 401
391        {
392            return Err(Error::Unauthorized(base.msg));
393        }
394
395        match serde_json::from_str::<T>(&raw_text) {
396            Ok(json) => {
397                debug!("Response status: {}, JSON: {:?}", status, json);
398                Ok(json)
399            }
400            Err(e) => {
401                debug!("JSON parse error: {}, raw response: {}", e, raw_text);
402                Err(Error::Json(e))
403            }
404        }
405    }
406}
407
408#[cfg(test)]
409mod tests {
410    use super::ApiV4Client;
411
412    #[test]
413    fn clear_token_removes_refresh_token() {
414        let mut client = ApiV4Client::new("https://example.com");
415        client.set_token("access_token".to_string());
416        client.set_refresh_token("refresh_token".to_string());
417
418        client.clear_token();
419
420        assert!(client.token.is_none());
421        assert!(client.get_refresh_token().is_none());
422    }
423}