|
18 | 18 | use crate::aws::checksum::Checksum; |
19 | 19 | use crate::aws::credential::{AwsCredential, CredentialExt}; |
20 | 20 | use crate::aws::{AwsCredentialProvider, STORE, STRICT_PATH_ENCODE_SET}; |
21 | | -use crate::client::list::ListResponse; |
22 | | -use crate::client::pagination::stream_paginated; |
| 21 | +use crate::client::get::GetClient; |
| 22 | +use crate::client::list::ListClient; |
| 23 | +use crate::client::list_response::ListResponse; |
23 | 24 | use crate::client::retry::RetryExt; |
24 | 25 | use crate::client::GetOptionsExt; |
25 | 26 | use crate::multipart::UploadPart; |
26 | 27 | use crate::path::DELIMITER; |
27 | | -use crate::util::format_prefix; |
28 | 28 | use crate::{ |
29 | | - BoxStream, ClientOptions, GetOptions, ListResult, MultipartId, Path, Result, |
30 | | - RetryConfig, StreamExt, |
| 29 | + ClientOptions, GetOptions, ListResult, MultipartId, Path, Result, RetryConfig, |
31 | 30 | }; |
| 31 | +use async_trait::async_trait; |
32 | 32 | use base64::prelude::BASE64_STANDARD; |
33 | 33 | use base64::Engine; |
34 | 34 | use bytes::{Buf, Bytes}; |
@@ -169,40 +169,6 @@ impl S3Client { |
169 | 169 | self.config.credentials.get_credential().await |
170 | 170 | } |
171 | 171 |
|
172 | | - /// Make an S3 GET request <https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html> |
173 | | - pub async fn get_request( |
174 | | - &self, |
175 | | - path: &Path, |
176 | | - options: GetOptions, |
177 | | - head: bool, |
178 | | - ) -> Result<Response> { |
179 | | - let credential = self.get_credential().await?; |
180 | | - let url = self.config.path_url(path); |
181 | | - let method = match head { |
182 | | - true => Method::HEAD, |
183 | | - false => Method::GET, |
184 | | - }; |
185 | | - |
186 | | - let builder = self.client.request(method, url); |
187 | | - |
188 | | - let response = builder |
189 | | - .with_get_options(options) |
190 | | - .with_aws_sigv4( |
191 | | - credential.as_ref(), |
192 | | - &self.config.region, |
193 | | - "s3", |
194 | | - self.config.sign_payload, |
195 | | - None, |
196 | | - ) |
197 | | - .send_retry(&self.config.retry_config) |
198 | | - .await |
199 | | - .context(GetRequestSnafu { |
200 | | - path: path.as_ref(), |
201 | | - })?; |
202 | | - |
203 | | - Ok(response) |
204 | | - } |
205 | | - |
206 | 172 | /// Make an S3 PUT request <https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html> |
207 | 173 | pub async fn put_request<T: Serialize + ?Sized + Sync>( |
208 | 174 | &self, |
@@ -302,88 +268,6 @@ impl S3Client { |
302 | 268 | Ok(()) |
303 | 269 | } |
304 | 270 |
|
305 | | - /// Make an S3 List request <https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html> |
306 | | - async fn list_request( |
307 | | - &self, |
308 | | - prefix: Option<&str>, |
309 | | - delimiter: bool, |
310 | | - token: Option<&str>, |
311 | | - offset: Option<&str>, |
312 | | - ) -> Result<(ListResult, Option<String>)> { |
313 | | - let credential = self.get_credential().await?; |
314 | | - let url = self.config.bucket_endpoint.clone(); |
315 | | - |
316 | | - let mut query = Vec::with_capacity(4); |
317 | | - |
318 | | - if let Some(token) = token { |
319 | | - query.push(("continuation-token", token)) |
320 | | - } |
321 | | - |
322 | | - if delimiter { |
323 | | - query.push(("delimiter", DELIMITER)) |
324 | | - } |
325 | | - |
326 | | - query.push(("list-type", "2")); |
327 | | - |
328 | | - if let Some(prefix) = prefix { |
329 | | - query.push(("prefix", prefix)) |
330 | | - } |
331 | | - |
332 | | - if let Some(offset) = offset { |
333 | | - query.push(("start-after", offset)) |
334 | | - } |
335 | | - |
336 | | - let response = self |
337 | | - .client |
338 | | - .request(Method::GET, &url) |
339 | | - .query(&query) |
340 | | - .with_aws_sigv4( |
341 | | - credential.as_ref(), |
342 | | - &self.config.region, |
343 | | - "s3", |
344 | | - self.config.sign_payload, |
345 | | - None, |
346 | | - ) |
347 | | - .send_retry(&self.config.retry_config) |
348 | | - .await |
349 | | - .context(ListRequestSnafu)? |
350 | | - .bytes() |
351 | | - .await |
352 | | - .context(ListResponseBodySnafu)?; |
353 | | - |
354 | | - let mut response: ListResponse = quick_xml::de::from_reader(response.reader()) |
355 | | - .context(InvalidListResponseSnafu)?; |
356 | | - let token = response.next_continuation_token.take(); |
357 | | - |
358 | | - Ok((response.try_into()?, token)) |
359 | | - } |
360 | | - |
361 | | - /// Perform a list operation automatically handling pagination |
362 | | - pub fn list_paginated( |
363 | | - &self, |
364 | | - prefix: Option<&Path>, |
365 | | - delimiter: bool, |
366 | | - offset: Option<&Path>, |
367 | | - ) -> BoxStream<'_, Result<ListResult>> { |
368 | | - let offset = offset.map(|x| x.to_string()); |
369 | | - let prefix = format_prefix(prefix); |
370 | | - stream_paginated( |
371 | | - (prefix, offset), |
372 | | - move |(prefix, offset), token| async move { |
373 | | - let (r, next_token) = self |
374 | | - .list_request( |
375 | | - prefix.as_deref(), |
376 | | - delimiter, |
377 | | - token.as_deref(), |
378 | | - offset.as_deref(), |
379 | | - ) |
380 | | - .await?; |
381 | | - Ok((r, (prefix, offset), next_token)) |
382 | | - }, |
383 | | - ) |
384 | | - .boxed() |
385 | | - } |
386 | | - |
387 | 271 | pub async fn create_multipart(&self, location: &Path) -> Result<MultipartId> { |
388 | 272 | let credential = self.get_credential().await?; |
389 | 273 | let url = format!("{}?uploads=", self.config.path_url(location),); |
@@ -451,6 +335,104 @@ impl S3Client { |
451 | 335 | } |
452 | 336 | } |
453 | 337 |
|
| 338 | +#[async_trait] |
| 339 | +impl GetClient for S3Client { |
| 340 | + const STORE: &'static str = STORE; |
| 341 | + |
| 342 | + /// Make an S3 GET request <https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html> |
| 343 | + async fn get_request( |
| 344 | + &self, |
| 345 | + path: &Path, |
| 346 | + options: GetOptions, |
| 347 | + head: bool, |
| 348 | + ) -> Result<Response> { |
| 349 | + let credential = self.get_credential().await?; |
| 350 | + let url = self.config.path_url(path); |
| 351 | + let method = match head { |
| 352 | + true => Method::HEAD, |
| 353 | + false => Method::GET, |
| 354 | + }; |
| 355 | + |
| 356 | + let builder = self.client.request(method, url); |
| 357 | + |
| 358 | + let response = builder |
| 359 | + .with_get_options(options) |
| 360 | + .with_aws_sigv4( |
| 361 | + credential.as_ref(), |
| 362 | + &self.config.region, |
| 363 | + "s3", |
| 364 | + self.config.sign_payload, |
| 365 | + None, |
| 366 | + ) |
| 367 | + .send_retry(&self.config.retry_config) |
| 368 | + .await |
| 369 | + .context(GetRequestSnafu { |
| 370 | + path: path.as_ref(), |
| 371 | + })?; |
| 372 | + |
| 373 | + Ok(response) |
| 374 | + } |
| 375 | +} |
| 376 | + |
| 377 | +#[async_trait] |
| 378 | +impl ListClient for S3Client { |
| 379 | + /// Make an S3 List request <https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html> |
| 380 | + async fn list_request( |
| 381 | + &self, |
| 382 | + prefix: Option<&str>, |
| 383 | + delimiter: bool, |
| 384 | + token: Option<&str>, |
| 385 | + offset: Option<&str>, |
| 386 | + ) -> Result<(ListResult, Option<String>)> { |
| 387 | + let credential = self.get_credential().await?; |
| 388 | + let url = self.config.bucket_endpoint.clone(); |
| 389 | + |
| 390 | + let mut query = Vec::with_capacity(4); |
| 391 | + |
| 392 | + if let Some(token) = token { |
| 393 | + query.push(("continuation-token", token)) |
| 394 | + } |
| 395 | + |
| 396 | + if delimiter { |
| 397 | + query.push(("delimiter", DELIMITER)) |
| 398 | + } |
| 399 | + |
| 400 | + query.push(("list-type", "2")); |
| 401 | + |
| 402 | + if let Some(prefix) = prefix { |
| 403 | + query.push(("prefix", prefix)) |
| 404 | + } |
| 405 | + |
| 406 | + if let Some(offset) = offset { |
| 407 | + query.push(("start-after", offset)) |
| 408 | + } |
| 409 | + |
| 410 | + let response = self |
| 411 | + .client |
| 412 | + .request(Method::GET, &url) |
| 413 | + .query(&query) |
| 414 | + .with_aws_sigv4( |
| 415 | + credential.as_ref(), |
| 416 | + &self.config.region, |
| 417 | + "s3", |
| 418 | + self.config.sign_payload, |
| 419 | + None, |
| 420 | + ) |
| 421 | + .send_retry(&self.config.retry_config) |
| 422 | + .await |
| 423 | + .context(ListRequestSnafu)? |
| 424 | + .bytes() |
| 425 | + .await |
| 426 | + .context(ListResponseBodySnafu)?; |
| 427 | + |
| 428 | + let mut response: ListResponse = quick_xml::de::from_reader(response.reader()) |
| 429 | + .context(InvalidListResponseSnafu)?; |
| 430 | + let token = response.next_continuation_token.take(); |
| 431 | + |
| 432 | + Ok((response.try_into()?, token)) |
| 433 | + } |
| 434 | +} |
| 435 | + |
454 | 436 | fn encode_path(path: &Path) -> PercentEncode<'_> { |
455 | 437 | utf8_percent_encode(path.as_ref(), &STRICT_PATH_ENCODE_SET) |
456 | 438 | } |
0 commit comments