1use bytes::Bytes;
2use lib_client_google_auth::AuthStrategy;
3use reqwest::header::HeaderMap;
4use serde::de::DeserializeOwned;
5use std::sync::Arc;
6use tracing::{debug, warn};
7
8use crate::error::{Error, Result};
9use crate::types::*;
10
11const DEFAULT_BASE_URL: &str = "https://www.googleapis.com/drive/v3";
12const UPLOAD_BASE_URL: &str = "https://www.googleapis.com/upload/drive/v3";
13
14pub struct ClientBuilder<A> {
15 auth: A,
16 base_url: String,
17}
18
19impl ClientBuilder<()> {
20 pub fn new() -> Self {
21 Self {
22 auth: (),
23 base_url: DEFAULT_BASE_URL.to_string(),
24 }
25 }
26
27 pub fn auth<S: AuthStrategy + 'static>(self, auth: S) -> ClientBuilder<S> {
28 ClientBuilder {
29 auth,
30 base_url: self.base_url,
31 }
32 }
33}
34
35impl Default for ClientBuilder<()> {
36 fn default() -> Self {
37 Self::new()
38 }
39}
40
41impl<A: AuthStrategy + 'static> ClientBuilder<A> {
42 pub fn base_url(mut self, url: impl Into<String>) -> Self {
43 self.base_url = url.into();
44 self
45 }
46
47 pub fn build(self) -> Client {
48 Client {
49 http: reqwest::Client::new(),
50 auth: Arc::new(self.auth),
51 base_url: self.base_url,
52 }
53 }
54}
55
56#[derive(Clone)]
57pub struct Client {
58 http: reqwest::Client,
59 auth: Arc<dyn AuthStrategy>,
60 base_url: String,
61}
62
63impl Client {
64 pub fn builder() -> ClientBuilder<()> {
65 ClientBuilder::new()
66 }
67
68 async fn request<T: DeserializeOwned>(
69 &self,
70 method: reqwest::Method,
71 path: &str,
72 body: Option<&impl serde::Serialize>,
73 ) -> Result<T> {
74 let url = format!("{}{}", self.base_url, path);
75 debug!("Drive API request: {} {}", method, url);
76
77 let mut headers = HeaderMap::new();
78 self.auth.apply(&mut headers).await?;
79
80 let mut request = self.http.request(method, &url).headers(headers);
81
82 if let Some(body) = body {
83 request = request.json(body);
84 }
85
86 let response = request.send().await?;
87 self.handle_response(response).await
88 }
89
90 async fn handle_response<T: DeserializeOwned>(&self, response: reqwest::Response) -> Result<T> {
91 let status = response.status();
92
93 if status.is_success() {
94 let body = response.text().await?;
95 serde_json::from_str(&body).map_err(Error::from)
96 } else {
97 let status_code = status.as_u16();
98 let body = response.text().await.unwrap_or_default();
99 warn!("Drive API error ({}): {}", status_code, body);
100
101 match status_code {
102 401 => Err(Error::Unauthorized),
103 403 => Err(Error::Forbidden(body)),
104 404 => Err(Error::NotFound(body)),
105 429 => Err(Error::RateLimited { retry_after: 60 }),
106 _ => Err(Error::Api {
107 status: status_code,
108 message: body,
109 }),
110 }
111 }
112 }
113
114 pub async fn get_file(&self, id: &str) -> Result<File> {
116 self.request(
117 reqwest::Method::GET,
118 &format!("/files/{}?fields=*", id),
119 None::<&()>,
120 )
121 .await
122 }
123
124 pub async fn list_files(
126 &self,
127 query: Option<&str>,
128 page_token: Option<&str>,
129 page_size: Option<u32>,
130 ) -> Result<FileList> {
131 let mut params = vec![("fields", "files(*),nextPageToken".to_string())];
132
133 if let Some(q) = query {
134 params.push(("q", q.to_string()));
135 }
136 if let Some(token) = page_token {
137 params.push(("pageToken", token.to_string()));
138 }
139 if let Some(size) = page_size {
140 params.push(("pageSize", size.to_string()));
141 }
142
143 let query_string: String = params
144 .iter()
145 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
146 .collect::<Vec<_>>()
147 .join("&");
148
149 self.request(
150 reqwest::Method::GET,
151 &format!("/files?{}", query_string),
152 None::<&()>,
153 )
154 .await
155 }
156
157 pub async fn create_file(&self, metadata: FileMetadata) -> Result<File> {
159 self.request(reqwest::Method::POST, "/files", Some(&metadata))
160 .await
161 }
162
163 pub async fn create_file_with_content(
165 &self,
166 metadata: FileMetadata,
167 content: Vec<u8>,
168 mime_type: &str,
169 ) -> Result<File> {
170 let url = format!("{}{}?uploadType=multipart&fields=*", UPLOAD_BASE_URL, "/files");
171 debug!("Drive API upload: POST {}", url);
172
173 let mut headers = HeaderMap::new();
174 self.auth.apply(&mut headers).await?;
175
176 let metadata_json = serde_json::to_string(&metadata)?;
177 let form = reqwest::multipart::Form::new()
178 .part(
179 "metadata",
180 reqwest::multipart::Part::text(metadata_json)
181 .mime_str("application/json")?,
182 )
183 .part(
184 "file",
185 reqwest::multipart::Part::bytes(content).mime_str(mime_type)?,
186 );
187
188 let response = self.http.post(&url).headers(headers).multipart(form).send().await?;
189 self.handle_response(response).await
190 }
191
192 pub async fn update_file(&self, id: &str, metadata: FileMetadata) -> Result<File> {
194 self.request(
195 reqwest::Method::PATCH,
196 &format!("/files/{}?fields=*", id),
197 Some(&metadata),
198 )
199 .await
200 }
201
202 pub async fn delete_file(&self, id: &str) -> Result<()> {
204 let url = format!("{}/files/{}", self.base_url, id);
205 debug!("Drive API request: DELETE {}", url);
206
207 let mut headers = HeaderMap::new();
208 self.auth.apply(&mut headers).await?;
209
210 let response = self.http.delete(&url).headers(headers).send().await?;
211 let status = response.status();
212
213 if status.is_success() {
214 Ok(())
215 } else {
216 let status_code = status.as_u16();
217 let body = response.text().await.unwrap_or_default();
218 Err(Error::Api {
219 status: status_code,
220 message: body,
221 })
222 }
223 }
224
225 pub async fn download_file(&self, id: &str) -> Result<Bytes> {
227 let url = format!("{}/files/{}?alt=media", self.base_url, id);
228 debug!("Drive API download: GET {}", url);
229
230 let mut headers = HeaderMap::new();
231 self.auth.apply(&mut headers).await?;
232
233 let response = self.http.get(&url).headers(headers).send().await?;
234 let status = response.status();
235
236 if status.is_success() {
237 Ok(response.bytes().await?)
238 } else {
239 let status_code = status.as_u16();
240 let body = response.text().await.unwrap_or_default();
241 Err(Error::Api {
242 status: status_code,
243 message: body,
244 })
245 }
246 }
247
248 pub async fn create_folder(&self, name: &str, parent: Option<&str>) -> Result<File> {
250 let mut metadata = FileMetadata::folder(name);
251 if let Some(p) = parent {
252 metadata = metadata.parent(p);
253 }
254 self.create_file(metadata).await
255 }
256
257 pub async fn share_file(&self, id: &str, permission: Permission) -> Result<Permission> {
259 self.request(
260 reqwest::Method::POST,
261 &format!("/files/{}/permissions", id),
262 Some(&permission),
263 )
264 .await
265 }
266
267 pub async fn list_permissions(&self, id: &str) -> Result<PermissionList> {
269 self.request(
270 reqwest::Method::GET,
271 &format!("/files/{}/permissions", id),
272 None::<&()>,
273 )
274 .await
275 }
276
277 pub async fn about(&self) -> Result<About> {
279 self.request(
280 reqwest::Method::GET,
281 "/about?fields=user,storageQuota",
282 None::<&()>,
283 )
284 .await
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291 use lib_client_google_auth::ApiKeyAuth;
292
293 #[test]
294 fn test_builder() {
295 let client = Client::builder()
296 .auth(ApiKeyAuth::new("test-key"))
297 .build();
298 assert_eq!(client.base_url, DEFAULT_BASE_URL);
299 }
300}