This is an automated email from the ASF dual-hosted git repository.

xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal.git


The following commit(s) were added to refs/heads/main by this push:
     new 9a50ef5780 fix(s3): parse MultipartUploadResponse to check error in 
body (#4735)
9a50ef5780 is described below

commit 9a50ef5780b34bb0aa459625b838dabadab2dfa2
Author: Ruihang Xia <[email protected]>
AuthorDate: Fri Jun 14 23:15:31 2024 +0800

    fix(s3): parse MultipartUploadResponse to check error in body (#4735)
---
 core/src/services/s3/error.rs  | 46 ++++++++++++++++++++++++++++++++++++------
 core/src/services/s3/writer.rs | 15 ++++++++++++--
 2 files changed, 53 insertions(+), 8 deletions(-)

diff --git a/core/src/services/s3/error.rs b/core/src/services/s3/error.rs
index 079789ef01..6602ddc396 100644
--- a/core/src/services/s3/error.rs
+++ b/core/src/services/s3/error.rs
@@ -16,6 +16,7 @@
 // under the License.
 
 use bytes::Buf;
+use http::response::Parts;
 use http::Response;
 use quick_xml::de;
 use serde::Deserialize;
@@ -24,13 +25,13 @@ use crate::raw::*;
 use crate::*;
 
 /// S3Error is the error returned by s3 service.
-#[derive(Default, Debug, Deserialize)]
+#[derive(Default, Debug, Deserialize, PartialEq, Eq)]
 #[serde(default, rename_all = "PascalCase")]
-struct S3Error {
-    code: String,
-    message: String,
-    resource: String,
-    request_id: String,
+pub(crate) struct S3Error {
+    pub code: String,
+    pub message: String,
+    pub resource: String,
+    pub request_id: String,
 }
 
 /// Parse error response into Error.
@@ -68,6 +69,21 @@ pub async fn parse_error(resp: Response<Buffer>) -> 
Result<Error> {
     Ok(err)
 }
 
+/// Util function to build [`Error`] from a [`S3Error`] object.
+pub(crate) fn from_s3_error(s3_error: S3Error, parts: Parts) -> Error {
+    let (kind, retryable) =
+        
parse_s3_error_code(s3_error.code.as_str()).unwrap_or((ErrorKind::Unexpected, 
false));
+    let mut err = Error::new(kind, &format!("{s3_error:?}"));
+
+    err = with_error_response_context(err, parts);
+
+    if retryable {
+        err = err.set_temporary();
+    }
+
+    err
+}
+
 /// Returns the `Error kind` of this code and whether the error is retryable.
 /// All possible error code: 
<https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html#ErrorCodeList>
 pub fn parse_s3_error_code(code: &str) -> Option<(ErrorKind, bool)> {
@@ -123,4 +139,22 @@ mod tests {
         assert_eq!(out.resource, "/mybucket/myfoto.jpg");
         assert_eq!(out.request_id, "4442587FB7D0A2F9");
     }
+
+    #[test]
+    fn test_parse_error_from_unrelated_input() {
+        let bs = bytes::Bytes::from(
+            r#"
+<?xml version="1.0" encoding="UTF-8"?>
+<CompleteMultipartUploadResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/";>
+  
<Location>http://Example-Bucket.s3.ap-southeast-1.amazonaws.com/Example-Object</Location>
+  <Bucket>Example-Bucket</Bucket>
+  <Key>Example-Object</Key> 
+  <ETag>"3858f62230ac3c915f300c664312c11f-9"</ETag>
+</CompleteMultipartUploadResult>
+"#,
+        );
+
+        let out: S3Error = de::from_reader(bs.reader()).expect("must success");
+        assert_eq!(out, S3Error::default());
+    }
 }
diff --git a/core/src/services/s3/writer.rs b/core/src/services/s3/writer.rs
index 3866404442..86c42f5683 100644
--- a/core/src/services/s3/writer.rs
+++ b/core/src/services/s3/writer.rs
@@ -21,7 +21,7 @@ use bytes::Buf;
 use http::StatusCode;
 
 use super::core::*;
-use super::error::parse_error;
+use super::error::{from_s3_error, parse_error, S3Error};
 use crate::raw::*;
 use crate::*;
 
@@ -158,7 +158,18 @@ impl oio::MultipartWrite for S3Writer {
         let status = resp.status();
 
         match status {
-            StatusCode::OK => Ok(()),
+            StatusCode::OK => {
+                // still check if there is any error because S3 might return 
error for status code 200
+                // 
https://docs.aws.amazon.com/AmazonS3/latest/API/API_CompleteMultipartUpload.html#API_CompleteMultipartUpload_Example_4
+                let (parts, body) = resp.into_parts();
+                let maybe_error: S3Error =
+                    
quick_xml::de::from_reader(body.reader()).map_err(new_xml_deserialize_error)?;
+                if !maybe_error.code.is_empty() {
+                    return Err(from_s3_error(maybe_error, parts));
+                }
+
+                Ok(())
+            }
             _ => Err(parse_error(resp).await?),
         }
     }

Reply via email to