1use serde::{Deserialize, Serialize};
6
7use crate::client::Client;
8use crate::client::http::HttpError;
9use crate::transport::auth::ServiceEndpoint;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
14pub enum FileState {
15 Processing,
17 Active,
19 Failed,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25#[serde(rename_all = "camelCase")]
26pub struct FileSource {
27 pub file_uri: String,
29 pub mime_type: String,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35#[serde(rename_all = "camelCase")]
36pub struct File {
37 #[serde(default)]
39 pub name: String,
40 #[serde(default)]
42 pub display_name: String,
43 #[serde(default)]
45 pub mime_type: String,
46 #[serde(default)]
48 pub size_bytes: Option<u64>,
49 #[serde(default)]
51 pub state: Option<FileState>,
52 #[serde(default)]
54 pub uri: Option<String>,
55 #[serde(default)]
57 pub sha256_hash: Option<String>,
58 #[serde(default)]
60 pub error: Option<serde_json::Value>,
61}
62
63#[derive(Debug, Clone)]
65pub struct UploadFileConfig {
66 pub display_name: Option<String>,
68 pub mime_type: String,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
74#[serde(rename_all = "camelCase")]
75pub struct ListFilesResponse {
76 #[serde(default)]
78 pub files: Vec<File>,
79 #[serde(default)]
81 pub next_page_token: Option<String>,
82}
83
84#[derive(Debug, thiserror::Error)]
86pub enum FilesError {
87 #[error(transparent)]
88 Http(#[from] HttpError),
90 #[error("Failed to parse response: {0}")]
91 Parse(#[from] serde_json::Error),
93 #[error("Auth error: {0}")]
94 Auth(#[from] crate::session::AuthError),
96 #[error("IO error: {0}")]
97 Io(#[from] std::io::Error),
99 #[error("Base64 decode error: {0}")]
100 Decode(String),
102}
103
104impl Client {
105 pub async fn list_files(&self) -> Result<ListFilesResponse, FilesError> {
107 let url = self.rest_url(ServiceEndpoint::Files);
108 let headers = self.auth_headers().await?;
109 let json = self.http_client().get_json(&url, headers).await?;
110 if json.is_null() {
112 return Ok(ListFilesResponse {
113 files: vec![],
114 next_page_token: None,
115 });
116 }
117 Ok(serde_json::from_value(json)?)
118 }
119
120 pub async fn get_file(&self, name: &str) -> Result<File, FilesError> {
122 let base_url = self.rest_url(ServiceEndpoint::Files);
123 let url = format!("{base_url}/{name}");
124 let headers = self.auth_headers().await?;
125 let json = self.http_client().get_json(&url, headers).await?;
126 Ok(serde_json::from_value(json)?)
127 }
128
129 pub async fn delete_file(&self, name: &str) -> Result<(), FilesError> {
131 let base_url = self.rest_url(ServiceEndpoint::Files);
132 let url = format!("{base_url}/{name}");
133 let headers = self.auth_headers().await?;
134 self.http_client().delete(&url, headers).await?;
135 Ok(())
136 }
137
138 pub async fn upload_file(
140 &self,
141 data: Vec<u8>,
142 config: UploadFileConfig,
143 ) -> Result<File, FilesError> {
144 let url = self.rest_url(ServiceEndpoint::Files);
145 let headers = self.auth_headers().await?;
146
147 let mut body = serde_json::json!({
148 "file": {
149 "mimeType": config.mime_type,
150 }
151 });
152 if let Some(name) = config.display_name {
153 body["file"]["displayName"] = serde_json::Value::String(name);
154 }
155
156 body["file"]["inlineData"] = serde_json::json!({
158 "mimeType": config.mime_type,
159 "data": base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &data),
160 });
161
162 let json = self.http_client().post_json(&url, headers, &body).await?;
163 Ok(serde_json::from_value(json)?)
164 }
165
166 pub async fn download_file(&self, name: &str) -> Result<Vec<u8>, FilesError> {
168 let base_url = self.rest_url(ServiceEndpoint::Files);
169 let url = format!("{base_url}/{name}:download");
170 let headers = self.auth_headers().await?;
171 let json = self.http_client().get_json(&url, headers).await?;
172
173 if let Some(data) = json.get("data").and_then(|v| v.as_str()) {
175 let bytes = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, data)
176 .map_err(|e| FilesError::Decode(e.to_string()))?;
177 Ok(bytes)
178 } else {
179 Ok(json.to_string().into_bytes())
181 }
182 }
183
184 pub async fn register_files(&self, sources: Vec<FileSource>) -> Result<Vec<File>, FilesError> {
186 let url = self.rest_url(ServiceEndpoint::Files);
187 let headers = self.auth_headers().await?;
188
189 let mut files = Vec::new();
190 for source in sources {
191 let body = serde_json::json!({
192 "file": {
193 "uri": source.file_uri,
194 "mimeType": source.mime_type,
195 }
196 });
197 let json = self
198 .http_client()
199 .post_json(&url, headers.clone(), &body)
200 .await?;
201 files.push(serde_json::from_value(json)?);
202 }
203 Ok(files)
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn parse_file() {
213 let json = serde_json::json!({
214 "name": "files/abc123",
215 "displayName": "test.txt",
216 "mimeType": "text/plain",
217 "sizeBytes": 1024,
218 "state": "ACTIVE",
219 "uri": "https://example.com/file"
220 });
221 let file: File = serde_json::from_value(json).unwrap();
222 assert_eq!(file.name, "files/abc123");
223 assert_eq!(file.display_name, "test.txt");
224 assert_eq!(file.mime_type, "text/plain");
225 assert_eq!(file.size_bytes, Some(1024));
226 assert_eq!(file.state, Some(FileState::Active));
227 }
228
229 #[test]
230 fn parse_list_files_response() {
231 let json = serde_json::json!({
232 "files": [
233 {
234 "name": "files/a",
235 "displayName": "a.txt",
236 "mimeType": "text/plain"
237 },
238 {
239 "name": "files/b",
240 "displayName": "b.pdf",
241 "mimeType": "application/pdf"
242 }
243 ],
244 "nextPageToken": "page2"
245 });
246 let resp: ListFilesResponse = serde_json::from_value(json).unwrap();
247 assert_eq!(resp.files.len(), 2);
248 assert_eq!(resp.next_page_token, Some("page2".to_string()));
249 }
250
251 #[test]
252 fn file_state_serialization() {
253 assert_eq!(
254 serde_json::to_value(FileState::Processing).unwrap(),
255 "PROCESSING"
256 );
257 assert_eq!(serde_json::to_value(FileState::Active).unwrap(), "ACTIVE");
258 assert_eq!(serde_json::to_value(FileState::Failed).unwrap(), "FAILED");
259 }
260
261 #[test]
262 fn file_state_deserialization() {
263 let state: FileState = serde_json::from_str("\"ACTIVE\"").unwrap();
264 assert_eq!(state, FileState::Active);
265 }
266
267 #[test]
268 fn file_source_serialization() {
269 let source = FileSource {
270 file_uri: "gs://bucket/file.txt".to_string(),
271 mime_type: "text/plain".to_string(),
272 };
273 let json = serde_json::to_value(&source).unwrap();
274 assert_eq!(json["fileUri"], "gs://bucket/file.txt");
275 assert_eq!(json["mimeType"], "text/plain");
276 }
277
278 #[test]
279 fn empty_list_response() {
280 let json = serde_json::json!({"files": []});
281 let resp: ListFilesResponse = serde_json::from_value(json).unwrap();
282 assert!(resp.files.is_empty());
283 assert!(resp.next_page_token.is_none());
284 }
285
286 #[test]
287 fn file_with_error() {
288 let json = serde_json::json!({
289 "name": "files/bad",
290 "displayName": "bad.txt",
291 "mimeType": "text/plain",
292 "state": "FAILED",
293 "error": {"code": 400, "message": "Invalid file"}
294 });
295 let file: File = serde_json::from_value(json).unwrap();
296 assert_eq!(file.state, Some(FileState::Failed));
297 assert!(file.error.is_some());
298 }
299}