[pbs-devel] [PATCH proxmox v7 3/9] s3 client: add dedicated type for s3 object keys

Christian Ebner c.ebner at proxmox.com
Thu Jul 10 19:06:44 CEST 2025


S3 objects are uniquely identified within a bucket by their object
key [0].

Implements conversion and utility traits to easily convert a string
as corresponding object key for the S3 storage backend. Keys might
either be full or relative. Full keys are not further expanded when
performing api requests, while relative keys are prefixed by the
common prefix as configured in the client. This allows for easy key
grouping based on client configuration and is used for PBS datastore
separation within the same bucket.

Further, this adds type checking for s3 client operations requiring
an object key.

[0] https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html

Signed-off-by: Christian Ebner <c.ebner at proxmox.com>
---
change since version 6:
- removed uri encoding in S3ObjectKey's, this is better handled by
  the client when generating the request uri.

 proxmox-s3-client/src/lib.rs        |  4 ++
 proxmox-s3-client/src/object_key.rs | 86 +++++++++++++++++++++++++++++
 2 files changed, 90 insertions(+)
 create mode 100644 proxmox-s3-client/src/object_key.rs

diff --git a/proxmox-s3-client/src/lib.rs b/proxmox-s3-client/src/lib.rs
index f65d123f..7286cbd1 100644
--- a/proxmox-s3-client/src/lib.rs
+++ b/proxmox-s3-client/src/lib.rs
@@ -14,3 +14,7 @@ pub use aws_sign_v4::uri_decode;
 mod client;
 #[cfg(feature = "impl")]
 pub use client::{S3Client, S3ClientOptions};
+#[cfg(feature = "impl")]
+mod object_key;
+#[cfg(feature = "impl")]
+pub use object_key::S3ObjectKey;
diff --git a/proxmox-s3-client/src/object_key.rs b/proxmox-s3-client/src/object_key.rs
new file mode 100644
index 00000000..49959b6e
--- /dev/null
+++ b/proxmox-s3-client/src/object_key.rs
@@ -0,0 +1,86 @@
+use anyhow::Error;
+
+#[derive(Clone, Debug)]
+/// S3 Object Key
+pub enum S3ObjectKey {
+    /// Object key which will not be prefixed any further by the client
+    Full(String),
+    /// Object key which will be expanded by the client with its configured common prefix
+    Relative(String),
+}
+
+impl core::convert::From<&str> for S3ObjectKey {
+    fn from(s: &str) -> Self {
+        if let Some(s) = s.strip_prefix("/") {
+            Self::Full(s.to_string())
+        } else {
+            Self::Relative(s.to_string())
+        }
+    }
+}
+impl S3ObjectKey {
+    /// Convert the given object key to a full key by extending it via given prefix
+    /// If the object key is already a full key, the prefix is ignored.
+    pub(crate) fn to_full_key(&self, prefix: &str) -> Self {
+        match self {
+            Self::Full(ref key) => Self::Full(key.to_string()),
+            Self::Relative(ref key) => {
+                let prefix = prefix.strip_prefix("/").unwrap_or(&prefix);
+                Self::Full(format!("{prefix}/{key}"))
+            }
+        }
+    }
+}
+
+impl std::ops::Deref for S3ObjectKey {
+    type Target = str;
+
+    fn deref(&self) -> &Self::Target {
+        match self {
+            Self::Full(key) => key,
+            Self::Relative(key) => key,
+        }
+    }
+}
+
+impl std::fmt::Display for S3ObjectKey {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Self::Full(key) => write!(f, "{key}"),
+            Self::Relative(key) => write!(f, "{key}"),
+        }
+    }
+}
+
+impl std::str::FromStr for S3ObjectKey {
+    type Err = Error;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        Ok(Self::from(s))
+    }
+}
+
+// Do not mangle with prefixes when de-serializing
+impl<'de> serde::Deserialize<'de> for S3ObjectKey {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        let object_key = std::borrow::Cow::<'de, str>::deserialize(deserializer)?.to_string();
+        Ok(Self::Full(object_key))
+    }
+}
+
+impl S3ObjectKey {
+    /// Generate source key for copy object operations given the source bucket.
+    /// Extends relative object key variants also by the given prefix.
+    pub fn to_copy_source_key(&self, source_bucket: &str, prefix: &str) -> Self {
+        match self {
+            Self::Full(key) => Self::Full(format!("{source_bucket}{key}")),
+            Self::Relative(key) => {
+                let prefix = prefix.strip_prefix("/").unwrap_or(&prefix);
+                Self::Full(format!("{source_bucket}/{prefix}/{key}"))
+            }
+        }
+    }
+}
-- 
2.47.2





More information about the pbs-devel mailing list