1use http::header;
19use http::Request;
20use http::Response;
21use serde::Deserialize;
22use std::fmt::Debug;
23use std::sync::Arc;
24
25use crate::raw::*;
26use crate::*;
27
28pub struct SwiftCore {
29 pub info: Arc<AccessorInfo>,
30 pub root: String,
31 pub endpoint: String,
32 pub container: String,
33 pub token: String,
34}
35
36impl Debug for SwiftCore {
37 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38 f.debug_struct("SwiftCore")
39 .field("root", &self.root)
40 .field("endpoint", &self.endpoint)
41 .field("container", &self.container)
42 .finish_non_exhaustive()
43 }
44}
45
46impl SwiftCore {
47 pub async fn swift_delete(&self, path: &str) -> Result<Response<Buffer>> {
48 let p = build_abs_path(&self.root, path);
49
50 let url = format!(
51 "{}/{}/{}",
52 &self.endpoint,
53 &self.container,
54 percent_encode_path(&p)
55 );
56
57 let mut req = Request::delete(&url);
58
59 req = req.header("X-Auth-Token", &self.token);
60
61 let body = Buffer::new();
62
63 let req = req
64 .extension(Operation::Delete)
65 .body(body)
66 .map_err(new_request_build_error)?;
67
68 self.info.http_client().send(req).await
69 }
70
71 pub async fn swift_list(
72 &self,
73 path: &str,
74 delimiter: &str,
75 limit: Option<usize>,
76 marker: &str,
77 ) -> Result<Response<Buffer>> {
78 let p = build_abs_path(&self.root, path);
79
80 let mut url = QueryPairsWriter::new(&format!("{}/{}/", &self.endpoint, &self.container,))
83 .push("prefix", &percent_encode_path(&p))
84 .push("delimiter", delimiter)
85 .push("format", "json");
86
87 if let Some(limit) = limit {
88 url = url.push("limit", &limit.to_string());
89 }
90 if !marker.is_empty() {
91 url = url.push("marker", marker);
92 }
93
94 let mut req = Request::get(url.finish());
95
96 req = req.header("X-Auth-Token", &self.token);
97
98 let req = req
99 .extension(Operation::List)
100 .body(Buffer::new())
101 .map_err(new_request_build_error)?;
102
103 self.info.http_client().send(req).await
104 }
105
106 pub async fn swift_create_object(
107 &self,
108 path: &str,
109 length: u64,
110 args: &OpWrite,
111 body: Buffer,
112 ) -> Result<Response<Buffer>> {
113 let p = build_abs_path(&self.root, path);
114 let url = format!(
115 "{}/{}/{}",
116 &self.endpoint,
117 &self.container,
118 percent_encode_path(&p)
119 );
120
121 let mut req = Request::put(&url);
122
123 if let Some(user_metadata) = args.user_metadata() {
125 for (k, v) in user_metadata {
126 req = req.header(format!("X-Object-Meta-{}", k), v);
127 }
128 }
129
130 req = req.header("X-Auth-Token", &self.token);
131 req = req.header(header::CONTENT_LENGTH, length);
132
133 let req = req
134 .extension(Operation::Write)
135 .body(body)
136 .map_err(new_request_build_error)?;
137
138 self.info.http_client().send(req).await
139 }
140
141 pub async fn swift_read(
142 &self,
143 path: &str,
144 range: BytesRange,
145 _arg: &OpRead,
146 ) -> Result<Response<HttpBody>> {
147 let p = build_abs_path(&self.root, path)
148 .trim_end_matches('/')
149 .to_string();
150
151 let url = format!(
152 "{}/{}/{}",
153 &self.endpoint,
154 &self.container,
155 percent_encode_path(&p)
156 );
157
158 let mut req = Request::get(&url);
159
160 req = req.header("X-Auth-Token", &self.token);
161
162 if !range.is_full() {
163 req = req.header(header::RANGE, range.to_header());
164 }
165
166 let req = req
167 .extension(Operation::Read)
168 .body(Buffer::new())
169 .map_err(new_request_build_error)?;
170
171 self.info.http_client().fetch(req).await
172 }
173
174 pub async fn swift_copy(&self, src_p: &str, dst_p: &str) -> Result<Response<Buffer>> {
175 let src_p = format!(
178 "/{}/{}",
179 self.container,
180 build_abs_path(&self.root, src_p).trim_end_matches('/')
181 );
182
183 let dst_p = build_abs_path(&self.root, dst_p)
184 .trim_end_matches('/')
185 .to_string();
186
187 let url = format!(
188 "{}/{}/{}",
189 &self.endpoint,
190 &self.container,
191 percent_encode_path(&dst_p)
192 );
193
194 let mut req = Request::put(&url);
197
198 req = req.header("X-Auth-Token", &self.token);
199 req = req.header("X-Copy-From", percent_encode_path(&src_p));
200
201 req = req.header("Content-Length", "0");
203
204 let body = Buffer::new();
205
206 let req = req
207 .extension(Operation::Copy)
208 .body(body)
209 .map_err(new_request_build_error)?;
210
211 self.info.http_client().send(req).await
212 }
213
214 pub async fn swift_get_metadata(&self, path: &str) -> Result<Response<Buffer>> {
215 let p = build_abs_path(&self.root, path);
216
217 let url = format!(
218 "{}/{}/{}",
219 &self.endpoint,
220 &self.container,
221 percent_encode_path(&p)
222 );
223
224 let mut req = Request::head(&url);
225
226 req = req.header("X-Auth-Token", &self.token);
227
228 let req = req
229 .extension(Operation::Stat)
230 .body(Buffer::new())
231 .map_err(new_request_build_error)?;
232
233 self.info.http_client().send(req).await
234 }
235}
236
237#[derive(Debug, Eq, PartialEq, Deserialize)]
238#[serde(untagged)]
239pub enum ListOpResponse {
240 Subdir {
241 subdir: String,
242 },
243 FileInfo {
244 bytes: u64,
245 hash: String,
246 name: String,
247 last_modified: String,
248 content_type: Option<String>,
249 },
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255
256 #[test]
257 fn parse_list_response_test() -> Result<()> {
258 let resp = bytes::Bytes::from(
259 r#"
260 [
261 {
262 "subdir": "animals/"
263 },
264 {
265 "subdir": "fruit/"
266 },
267 {
268 "bytes": 147,
269 "hash": "5e6b5b70b0426b1cc1968003e1afa5ad",
270 "name": "test.txt",
271 "content_type": "text/plain",
272 "last_modified": "2023-11-01T03:00:23.147480"
273 }
274 ]
275 "#,
276 );
277
278 let mut out = serde_json::from_slice::<Vec<ListOpResponse>>(&resp)
279 .map_err(new_json_deserialize_error)?;
280
281 assert_eq!(out.len(), 3);
282 assert_eq!(
283 out.pop().unwrap(),
284 ListOpResponse::FileInfo {
285 bytes: 147,
286 hash: "5e6b5b70b0426b1cc1968003e1afa5ad".to_string(),
287 name: "test.txt".to_string(),
288 last_modified: "2023-11-01T03:00:23.147480".to_string(),
289 content_type: Some("text/plain".to_string()),
290 }
291 );
292
293 assert_eq!(
294 out.pop().unwrap(),
295 ListOpResponse::Subdir {
296 subdir: "fruit/".to_string()
297 }
298 );
299
300 assert_eq!(
301 out.pop().unwrap(),
302 ListOpResponse::Subdir {
303 subdir: "animals/".to_string()
304 }
305 );
306
307 Ok(())
308 }
309}