mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-06 05:17:42 +00:00
refactor(storage): extract ListParts response assembly helper (#1812)
This commit is contained in:
@@ -24,6 +24,8 @@ use crate::storage::helper::OperationHelper;
|
||||
use crate::storage::options::{filter_object_metadata, get_content_sha256};
|
||||
use crate::storage::readers::InMemoryAsyncReader;
|
||||
use crate::storage::s3_api::bucket::{build_list_objects_output, build_list_objects_v2_output};
|
||||
use crate::storage::s3_api::common::rustfs_owner;
|
||||
use crate::storage::s3_api::multipart::build_list_parts_output;
|
||||
use crate::storage::sse::{
|
||||
DecryptionRequest, EncryptionRequest, PrepareEncryptionRequest, check_encryption_metadata, sse_decryption, sse_encryption,
|
||||
sse_prepare_encryption, strip_managed_encryption_metadata,
|
||||
@@ -164,10 +166,8 @@ macro_rules! try_ {
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) static RUSTFS_OWNER: LazyLock<Owner> = LazyLock::new(|| Owner {
|
||||
display_name: Some("rustfs".to_owned()),
|
||||
id: Some("c19050dbcee97fda828689dda99097a6321af2248fa760517237346e5d9c8a66".to_owned()),
|
||||
});
|
||||
// Shared owner metadata source for S3 response compatibility.
|
||||
pub(crate) static RUSTFS_OWNER: LazyLock<Owner> = LazyLock::new(rustfs_owner);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FS {
|
||||
@@ -3846,38 +3846,7 @@ impl S3 for FS {
|
||||
.await
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let output = ListPartsOutput {
|
||||
bucket: Some(res.bucket),
|
||||
key: Some(res.object),
|
||||
upload_id: Some(res.upload_id),
|
||||
parts: Some(
|
||||
res.parts
|
||||
.into_iter()
|
||||
.map(|p| Part {
|
||||
e_tag: p.etag.map(|etag| to_s3s_etag(&etag)),
|
||||
last_modified: p.last_mod.map(Timestamp::from),
|
||||
part_number: Some(p.part_num as i32),
|
||||
size: Some(p.size as i64),
|
||||
..Default::default()
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
owner: Some(RUSTFS_OWNER.to_owned()),
|
||||
initiator: Some(Initiator {
|
||||
id: RUSTFS_OWNER.id.clone(),
|
||||
display_name: RUSTFS_OWNER.display_name.clone(),
|
||||
}),
|
||||
is_truncated: Some(res.is_truncated),
|
||||
next_part_number_marker: res.next_part_number_marker.try_into().ok(),
|
||||
max_parts: res.max_parts.try_into().ok(),
|
||||
part_number_marker: res.part_number_marker.try_into().ok(),
|
||||
storage_class: if res.storage_class.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(res.storage_class.into())
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
let output = build_list_parts_output(res);
|
||||
Ok(S3Response::new(output))
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2024 RustFS Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use s3s::dto::{Initiator, Owner};
|
||||
|
||||
// Stable owner identity used in S3 response payloads (for example ACL, ListBuckets, and multipart listings).
|
||||
// RustFS currently does not maintain a persistent per-tenant canonical owner ID, so we keep a deterministic
|
||||
// placeholder to preserve response compatibility and test stability.
|
||||
// This value is metadata-only and must not be used for authentication/authorization decisions.
|
||||
pub(crate) const RUSTFS_OWNER_DISPLAY_NAME: &str = "rustfs";
|
||||
pub(crate) const RUSTFS_OWNER_ID: &str = "c19050dbcee97fda828689dda99097a6321af2248fa760517237346e5d9c8a66";
|
||||
|
||||
pub(crate) fn rustfs_owner() -> Owner {
|
||||
Owner {
|
||||
display_name: Some(RUSTFS_OWNER_DISPLAY_NAME.to_owned()),
|
||||
id: Some(RUSTFS_OWNER_ID.to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn rustfs_initiator() -> Initiator {
|
||||
Initiator {
|
||||
id: Some(RUSTFS_OWNER_ID.to_owned()),
|
||||
display_name: Some(RUSTFS_OWNER_DISPLAY_NAME.to_owned()),
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,9 @@
|
||||
|
||||
pub(crate) mod acl {}
|
||||
pub(crate) mod bucket;
|
||||
pub(crate) mod common;
|
||||
pub(crate) mod encryption {}
|
||||
pub(crate) mod multipart {}
|
||||
pub(crate) mod multipart;
|
||||
/// Object helper facade placeholder.
|
||||
///
|
||||
/// Read-path helpers shared across storage components should live in neutral
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
// Copyright 2024 RustFS Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::storage::s3_api::common::{rustfs_initiator, rustfs_owner};
|
||||
use rustfs_ecstore::client::object_api_utils::to_s3s_etag;
|
||||
use rustfs_ecstore::store_api::ListPartsInfo;
|
||||
use s3s::dto::{ListPartsOutput, Part, Timestamp};
|
||||
|
||||
pub(crate) fn build_list_parts_output(res: ListPartsInfo) -> ListPartsOutput {
|
||||
let owner = rustfs_owner();
|
||||
let initiator = rustfs_initiator();
|
||||
|
||||
ListPartsOutput {
|
||||
bucket: Some(res.bucket),
|
||||
key: Some(res.object),
|
||||
upload_id: Some(res.upload_id),
|
||||
parts: Some(
|
||||
res.parts
|
||||
.into_iter()
|
||||
.map(|p| Part {
|
||||
e_tag: p.etag.map(|etag| to_s3s_etag(&etag)),
|
||||
last_modified: p.last_mod.map(Timestamp::from),
|
||||
part_number: p.part_num.try_into().ok(),
|
||||
size: p.size.try_into().ok(),
|
||||
..Default::default()
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
owner: Some(owner),
|
||||
initiator: Some(initiator),
|
||||
is_truncated: Some(res.is_truncated),
|
||||
next_part_number_marker: res.next_part_number_marker.try_into().ok(),
|
||||
max_parts: res.max_parts.try_into().ok(),
|
||||
part_number_marker: res.part_number_marker.try_into().ok(),
|
||||
storage_class: if res.storage_class.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(res.storage_class.into())
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::build_list_parts_output;
|
||||
use rustfs_ecstore::client::object_api_utils::to_s3s_etag;
|
||||
use rustfs_ecstore::store_api::{ListPartsInfo, PartInfo};
|
||||
|
||||
#[test]
|
||||
fn test_list_parts_output_maps_parts_and_owner() {
|
||||
let input = ListPartsInfo {
|
||||
bucket: "bucket-a".to_string(),
|
||||
object: "obj-a".to_string(),
|
||||
upload_id: "upload-a".to_string(),
|
||||
storage_class: "STANDARD".to_string(),
|
||||
part_number_marker: 1,
|
||||
next_part_number_marker: 2,
|
||||
max_parts: 1000,
|
||||
is_truncated: true,
|
||||
parts: vec![PartInfo {
|
||||
part_num: 1,
|
||||
size: 11,
|
||||
etag: Some("etag-1".to_string()),
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let output = build_list_parts_output(input);
|
||||
let parts = output.parts.as_ref().expect("parts should be present");
|
||||
|
||||
assert_eq!(output.bucket.as_deref(), Some("bucket-a"));
|
||||
assert_eq!(output.key.as_deref(), Some("obj-a"));
|
||||
assert_eq!(output.upload_id.as_deref(), Some("upload-a"));
|
||||
assert_eq!(output.storage_class.as_ref().map(|v| v.as_str()), Some("STANDARD"));
|
||||
assert_eq!(output.part_number_marker, Some(1));
|
||||
assert_eq!(output.next_part_number_marker, Some(2));
|
||||
assert_eq!(output.max_parts, Some(1000));
|
||||
assert_eq!(output.is_truncated, Some(true));
|
||||
assert_eq!(parts.len(), 1);
|
||||
assert_eq!(parts[0].part_number, Some(1));
|
||||
assert_eq!(parts[0].size, Some(11));
|
||||
assert_eq!(parts[0].e_tag, Some(to_s3s_etag("etag-1")));
|
||||
assert!(output.owner.is_some());
|
||||
assert!(output.initiator.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_parts_output_handles_empty_storage_class_and_overflow_markers() {
|
||||
let input = ListPartsInfo {
|
||||
storage_class: String::new(),
|
||||
part_number_marker: usize::MAX,
|
||||
next_part_number_marker: usize::MAX,
|
||||
max_parts: usize::MAX,
|
||||
parts: vec![PartInfo {
|
||||
part_num: usize::MAX,
|
||||
size: usize::MAX,
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let output = build_list_parts_output(input);
|
||||
let parts = output.parts.as_ref().expect("parts should be present");
|
||||
|
||||
assert_eq!(output.storage_class, None);
|
||||
assert_eq!(output.part_number_marker, None);
|
||||
assert_eq!(output.next_part_number_marker, None);
|
||||
assert_eq!(output.max_parts, None);
|
||||
assert_eq!(parts.len(), 1);
|
||||
assert_eq!(parts[0].part_number, None);
|
||||
assert_eq!(parts[0].size, None);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user