Skip to main content

s3m/s3/actions/
abortmultipartupload.rs

1//! Amazon S3 multipart upload limits
2//! Maximum object size 5 TB
3//! Maximum number of parts per upload  10,000
4//! <https://docs.aws.amazon.com/AmazonS3/latest/dev/qfacts.html>
5
6use crate::{
7    s3::actions::{Action, response_error},
8    s3::{S3, request, tools},
9};
10use anyhow::{Result, anyhow};
11use reqwest::Method;
12use std::collections::BTreeMap;
13
14#[derive(Debug, Default)]
15pub struct AbortMultipartUpload<'a> {
16    key: &'a str,
17    upload_id: &'a str,
18}
19
20impl<'a> AbortMultipartUpload<'a> {
21    #[must_use]
22    pub const fn new(key: &'a str, upload_id: &'a str) -> Self {
23        Self { key, upload_id }
24    }
25
26    /// # Errors
27    ///
28    /// Will return `Err` if can not make the request
29    pub async fn request(&self, s3: &S3) -> Result<String> {
30        let (url, headers) = &self.sign(s3, tools::sha256_digest("").as_ref(), None, None)?;
31
32        let response = request::request(
33            s3.client(),
34            url.clone(),
35            self.http_method()?,
36            headers,
37            None,
38            None,
39            None,
40        )
41        .await?;
42
43        if response.status().is_success() {
44            Ok(response.text().await?)
45        } else {
46            Err(anyhow!(response_error(response).await?))
47        }
48    }
49}
50
51// https://docs.aws.amazon.com/AmazonS3/latest/API/API_AbortMultipartUpload.html
52impl Action for AbortMultipartUpload<'_> {
53    fn http_method(&self) -> Result<Method> {
54        Ok(Method::from_bytes(b"DELETE")?)
55    }
56
57    fn headers(&self) -> Option<BTreeMap<&str, &str>> {
58        None
59    }
60
61    fn query_pairs(&self) -> Option<BTreeMap<&str, &str>> {
62        // URL query_pairs
63        let mut map: BTreeMap<&str, &str> = BTreeMap::new();
64
65        // uploadId - Upload ID that identifies the multipart upload.
66        map.insert("uploadId", self.upload_id);
67
68        Some(map)
69    }
70
71    fn path(&self) -> Option<Vec<&str>> {
72        Some(self.key.split('/').collect())
73    }
74}
75
76#[cfg(test)]
77#[allow(
78    clippy::unwrap_used,
79    clippy::expect_used,
80    clippy::panic,
81    clippy::indexing_slicing,
82    clippy::unnecessary_wraps
83)]
84mod tests {
85    use super::*;
86    use crate::s3::{Credentials, Region, S3};
87    use secrecy::SecretString;
88
89    #[test]
90    fn test_method() {
91        let action = AbortMultipartUpload::new("key", "uid");
92        assert_eq!(Method::DELETE, action.http_method().unwrap());
93    }
94
95    #[test]
96    fn test_query_pairs() {
97        let action = AbortMultipartUpload::new("key", "uid");
98        let mut map = BTreeMap::new();
99        map.insert("uploadId", "uid");
100        assert_eq!(Some(map), action.query_pairs());
101    }
102
103    #[test]
104    fn test_path() {
105        let action = AbortMultipartUpload::new("key", "uid");
106        assert_eq!(Some(vec!["key"]), action.path());
107    }
108
109    #[test]
110    fn test_sign() {
111        let s3 = S3::new(
112            &Credentials::new(
113                "AKIAIOSFODNN7EXAMPLE",
114                &SecretString::new("wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY".into()),
115            ),
116            &"us-west-1".parse::<Region>().unwrap(),
117            Some("awsexamplebucket1".to_string()),
118            false,
119        );
120        let action = AbortMultipartUpload::new("key", "uid");
121        let (url, headers) = action
122            .sign(&s3, tools::sha256_digest("").as_ref(), None, None)
123            .unwrap();
124        assert_eq!(
125            "https://s3.us-west-1.amazonaws.com/awsexamplebucket1/key?uploadId=uid",
126            url.as_str()
127        );
128        assert!(
129            headers
130                .get("authorization")
131                .unwrap()
132                .starts_with("AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE")
133        );
134    }
135}