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.body(body).map_err(new_request_build_error)?;
64
65 self.info.http_client().send(req).await
66 }
67
68 pub async fn swift_list(
69 &self,
70 path: &str,
71 delimiter: &str,
72 limit: Option<usize>,
73 marker: &str,
74 ) -> Result<Response<Buffer>> {
75 let p = build_abs_path(&self.root, path);
76
77 let mut url = format!(
80 "{}/{}/?prefix={}&delimiter={}&format=json",
81 &self.endpoint,
82 &self.container,
83 percent_encode_path(&p),
84 delimiter
85 );
86
87 if let Some(limit) = limit {
88 url += &format!("&limit={}", limit);
89 }
90 if !marker.is_empty() {
91 url += &format!("&marker={}", marker);
92 }
93
94 let mut req = Request::get(&url);
95
96 req = req.header("X-Auth-Token", &self.token);
97
98 let req = req.body(Buffer::new()).map_err(new_request_build_error)?;
99
100 self.info.http_client().send(req).await
101 }
102
103 pub async fn swift_create_object(
104 &self,
105 path: &str,
106 length: u64,
107 args: &OpWrite,
108 body: Buffer,
109 ) -> Result<Response<Buffer>> {
110 let p = build_abs_path(&self.root, path);
111 let url = format!(
112 "{}/{}/{}",
113 &self.endpoint,
114 &self.container,
115 percent_encode_path(&p)
116 );
117
118 let mut req = Request::put(&url);
119
120 if let Some(user_metadata) = args.user_metadata() {
122 for (k, v) in user_metadata {
123 req = req.header(format!("X-Object-Meta-{}", k), v);
124 }
125 }
126
127 req = req.header("X-Auth-Token", &self.token);
128 req = req.header(header::CONTENT_LENGTH, length);
129
130 let req = req.body(body).map_err(new_request_build_error)?;
131
132 self.info.http_client().send(req).await
133 }
134
135 pub async fn swift_read(
136 &self,
137 path: &str,
138 range: BytesRange,
139 _arg: &OpRead,
140 ) -> Result<Response<HttpBody>> {
141 let p = build_abs_path(&self.root, path)
142 .trim_end_matches('/')
143 .to_string();
144
145 let url = format!(
146 "{}/{}/{}",
147 &self.endpoint,
148 &self.container,
149 percent_encode_path(&p)
150 );
151
152 let mut req = Request::get(&url);
153
154 req = req.header("X-Auth-Token", &self.token);
155
156 if !range.is_full() {
157 req = req.header(header::RANGE, range.to_header());
158 }
159
160 let req = req.body(Buffer::new()).map_err(new_request_build_error)?;
161
162 self.info.http_client().fetch(req).await
163 }
164
165 pub async fn swift_copy(&self, src_p: &str, dst_p: &str) -> Result<Response<Buffer>> {
166 let src_p = format!(
169 "/{}/{}",
170 self.container,
171 build_abs_path(&self.root, src_p).trim_end_matches('/')
172 );
173
174 let dst_p = build_abs_path(&self.root, dst_p)
175 .trim_end_matches('/')
176 .to_string();
177
178 let url = format!(
179 "{}/{}/{}",
180 &self.endpoint,
181 &self.container,
182 percent_encode_path(&dst_p)
183 );
184
185 let mut req = Request::put(&url);
188
189 req = req.header("X-Auth-Token", &self.token);
190 req = req.header("X-Copy-From", percent_encode_path(&src_p));
191
192 req = req.header("Content-Length", "0");
194
195 let body = Buffer::new();
196
197 let req = req.body(body).map_err(new_request_build_error)?;
198
199 self.info.http_client().send(req).await
200 }
201
202 pub async fn swift_get_metadata(&self, path: &str) -> Result<Response<Buffer>> {
203 let p = build_abs_path(&self.root, path);
204
205 let url = format!(
206 "{}/{}/{}",
207 &self.endpoint,
208 &self.container,
209 percent_encode_path(&p)
210 );
211
212 let mut req = Request::head(&url);
213
214 req = req.header("X-Auth-Token", &self.token);
215
216 let req = req.body(Buffer::new()).map_err(new_request_build_error)?;
217
218 self.info.http_client().send(req).await
219 }
220}
221
222#[derive(Debug, Eq, PartialEq, Deserialize)]
223#[serde(untagged)]
224pub enum ListOpResponse {
225 Subdir {
226 subdir: String,
227 },
228 FileInfo {
229 bytes: u64,
230 hash: String,
231 name: String,
232 last_modified: String,
233 content_type: Option<String>,
234 },
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240
241 #[test]
242 fn parse_list_response_test() -> Result<()> {
243 let resp = bytes::Bytes::from(
244 r#"
245 [
246 {
247 "subdir": "animals/"
248 },
249 {
250 "subdir": "fruit/"
251 },
252 {
253 "bytes": 147,
254 "hash": "5e6b5b70b0426b1cc1968003e1afa5ad",
255 "name": "test.txt",
256 "content_type": "text/plain",
257 "last_modified": "2023-11-01T03:00:23.147480"
258 }
259 ]
260 "#,
261 );
262
263 let mut out = serde_json::from_slice::<Vec<ListOpResponse>>(&resp)
264 .map_err(new_json_deserialize_error)?;
265
266 assert_eq!(out.len(), 3);
267 assert_eq!(
268 out.pop().unwrap(),
269 ListOpResponse::FileInfo {
270 bytes: 147,
271 hash: "5e6b5b70b0426b1cc1968003e1afa5ad".to_string(),
272 name: "test.txt".to_string(),
273 last_modified: "2023-11-01T03:00:23.147480".to_string(),
274 content_type: Some("text/plain".to_string()),
275 }
276 );
277
278 assert_eq!(
279 out.pop().unwrap(),
280 ListOpResponse::Subdir {
281 subdir: "fruit/".to_string()
282 }
283 );
284
285 assert_eq!(
286 out.pop().unwrap(),
287 ListOpResponse::Subdir {
288 subdir: "animals/".to_string()
289 }
290 );
291
292 Ok(())
293 }
294}