From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 8917F1FF138 for ; Wed, 04 Mar 2026 14:59:06 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id ECF1FB217; Wed, 4 Mar 2026 15:00:10 +0100 (CET) From: Christian Ebner To: pbs-devel@lists.proxmox.com Subject: [PATCH proxmox v2 2/4] s3-client: return dedicated error type for delete object response Date: Wed, 4 Mar 2026 14:59:18 +0100 Message-ID: <20260304135922.717714-3-c.ebner@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260304135922.717714-1-c.ebner@proxmox.com> References: <20260304135922.717714-1-c.ebner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1772632750688 X-SPAM-LEVEL: Spam detection results: 0 AWL -1.013 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment RCVD_IN_VALIDITY_CERTIFIED_BLOCKED 0.668 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_RPBL_BLOCKED 0.322 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_SAFE_BLOCKED 1.141 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: OPURZ55MTI4F7VXNWRQ3M4GGY5EMBO5S X-Message-ID-Hash: OPURZ55MTI4F7VXNWRQ3M4GGY5EMBO5S X-MailFrom: c.ebner@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Backup Server development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: In order to be able to reuse the delete object API calls as opt-in replacement for delete objects API calls, but not suppress the HTTP status code on failure. Use an enum to distinguish the two cases and implement the Into trait on it to be albe to easily convert back to an anyhow::Error type. Signed-off-by: Christian Ebner --- changes since version 1: - not present in previous version proxmox-s3-client/src/client.rs | 5 ++- proxmox-s3-client/src/response_reader.rs | 39 +++++++++++++++++++++--- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/proxmox-s3-client/src/client.rs b/proxmox-s3-client/src/client.rs index 3d0af5d6..e536614b 100644 --- a/proxmox-s3-client/src/client.rs +++ b/proxmox-s3-client/src/client.rs @@ -554,7 +554,10 @@ impl S3Client { let response = self.send(request, None).await?; let response_reader = ResponseReader::new(response); - response_reader.delete_object_response(object_key).await + response_reader + .delete_object_response(object_key) + .await + .map_err(Into::into) } /// Delete multiple objects from a bucket using a single HTTP request. diff --git a/proxmox-s3-client/src/response_reader.rs b/proxmox-s3-client/src/response_reader.rs index be7c0950..3478ea7c 100644 --- a/proxmox-s3-client/src/response_reader.rs +++ b/proxmox-s3-client/src/response_reader.rs @@ -1,6 +1,6 @@ use std::str::FromStr; -use anyhow::{anyhow, bail, Context, Error}; +use anyhow::{anyhow, bail, format_err, Context, Error}; use http_body_util::BodyExt; use hyper::body::{Bytes, Incoming}; use hyper::header::HeaderName; @@ -226,6 +226,26 @@ pub struct Bucket { pub creation_date: LastModifiedTimestamp, } +pub(crate) enum DeleteError { + Response(DeleteObjectError), + Parsing(Error), +} + +impl Into for DeleteError { + fn into(self) -> Error { + match self { + Self::Response(delete_error) => { + if let Some(code) = delete_error.code { + format_err!("unexpected status code: {code}") + } else { + format_err!("failed to generate error, missing error status code") + } + } + Self::Parsing(error) => error, + } + } +} + impl ResponseReader { /// Create a new response reader to parse given response. pub(crate) fn new(response: Response) -> Self { @@ -364,22 +384,31 @@ impl ResponseReader { pub(crate) async fn delete_object_response( self, key: S3ObjectKey, - ) -> Result { + ) -> Result { let (parts, _body) = self.response.into_parts(); match parts.status { StatusCode::NO_CONTENT => (), - status_code => bail!("unexpected status code {status_code}"), + status_code => { + return Err(DeleteError::Response(DeleteObjectError { + code: Some(status_code.to_string()), + key: Some(key), + message: None, + version_id: None, + })); + } }; let delete_marker = Self::parse_optional_header( HeaderName::from_static("x-amz-delete-marker"), &parts.headers, - )?; + ) + .map_err(|err| DeleteError::Parsing(err))?; let delete_marker_version_id = Self::parse_optional_header( HeaderName::from_static("x-amz-version-id"), &parts.headers, - )?; + ) + .map_err(|err| DeleteError::Parsing(err))?; Ok(DeletedObject { delete_marker, -- 2.47.3