refactor: move object list response contracts (#3548)

This commit is contained in:
安正超
2026-06-18 07:19:07 +08:00
committed by GitHub
parent 80ed63484b
commit 24fa03e04b
6 changed files with 138 additions and 86 deletions
+5 -59
View File
@@ -4,6 +4,11 @@ use rustfs_storage_api::{
VersionMarker, WalkVersionsSortOrder,
};
pub type ListObjectsInfo = rustfs_storage_api::ListObjectsInfo<ObjectInfo>;
pub type ListObjectsV2Info = rustfs_storage_api::ListObjectsV2Info<ObjectInfo>;
pub type ListObjectVersionsInfo = rustfs_storage_api::ListObjectVersionsInfo<ObjectInfo>;
pub type ObjectInfoOrErr = rustfs_storage_api::ObjectInfoOrErr<ObjectInfo, Error>;
#[derive(Debug, Default, Clone)]
pub struct ObjectOptions {
// Use the maximum parity (N/2), used when saving server configuration files
@@ -708,50 +713,6 @@ fn versions_after_marker(file_infos: &rustfs_filemeta::FileInfoVersions, marker:
.unwrap_or(&file_infos.versions)
}
#[derive(Debug, Default)]
pub struct ListObjectsInfo {
// Indicates whether the returned list objects response is truncated. A
// value of true indicates that the list was truncated. The list can be truncated
// if the number of objects exceeds the limit allowed or specified
// by max keys.
pub is_truncated: bool,
// When response is truncated (the IsTruncated element value in the response
// is true), you can use the key name in this field as marker in the subsequent
// request to get next set of objects.
pub next_marker: Option<String>,
// List of objects info for this request.
pub objects: Vec<ObjectInfo>,
// List of prefixes for this request.
pub prefixes: Vec<String>,
}
#[derive(Debug, Default)]
pub struct ListObjectsV2Info {
// Indicates whether the returned list objects response is truncated. A
// value of true indicates that the list was truncated. The list can be truncated
// if the number of objects exceeds the limit allowed or specified
// by max keys.
pub is_truncated: bool,
// When response is truncated (the IsTruncated element value in the response
// is true), you can use the key name in this field as marker in the subsequent
// request to get next set of objects.
//
// NOTE: This element is returned only if you have delimiter request parameter
// specified.
pub continuation_token: Option<String>,
pub next_continuation_token: Option<String>,
// List of objects info for this request.
pub objects: Vec<ObjectInfo>,
// List of prefixes for this request.
pub prefixes: Vec<String>,
}
#[derive(Debug, Default, Clone)]
pub struct ObjectToDelete {
pub object_name: String,
@@ -805,15 +766,6 @@ impl DeletedObject {
}
}
#[derive(Debug, Default, Clone)]
pub struct ListObjectVersionsInfo {
pub is_truncated: bool,
pub next_marker: Option<String>,
pub next_version_idmarker: Option<String>,
pub objects: Vec<ObjectInfo>,
pub prefixes: Vec<String>,
}
type WalkFilter = fn(&FileInfo) -> bool;
#[derive(Clone, Default)]
@@ -827,12 +779,6 @@ pub struct WalkOptions {
pub include_free_versions: bool, // include persisted tier free-version cleanup records
}
#[derive(Debug)]
pub struct ObjectInfoOrErr {
pub item: Option<ObjectInfo>,
pub err: Option<Error>,
}
#[cfg(test)]
mod tests {
use super::*;
+1
View File
@@ -25,5 +25,6 @@ pub use bucket::{BucketInfo, BucketOperations, BucketOptions, DeleteBucketOption
pub use error::{StorageErrorCode, StorageResult};
pub use multipart::{CompletePart, ListMultipartsInfo, ListPartsInfo, MultipartInfo, MultipartUploadResult, PartInfo};
pub use object::{HTTPPreconditions, HTTPRangeError, HTTPRangeSpec, ObjectLockRetentionOptions};
pub use object::{ListObjectVersionsInfo, ListObjectsInfo, ListObjectsV2Info, ObjectInfoOrErr};
pub use object::{ObjectPreconditionError, ObjectPreconditionPart, ObjectPreconditionState};
pub use object::{VersionMarker, WalkVersionsSortOrder};
+66
View File
@@ -154,6 +154,38 @@ pub enum WalkVersionsSortOrder {
Descending,
}
#[derive(Debug, Default)]
pub struct ListObjectsInfo<ObjectItem> {
pub is_truncated: bool,
pub next_marker: Option<String>,
pub objects: Vec<ObjectItem>,
pub prefixes: Vec<String>,
}
#[derive(Debug, Default)]
pub struct ListObjectsV2Info<ObjectItem> {
pub is_truncated: bool,
pub continuation_token: Option<String>,
pub next_continuation_token: Option<String>,
pub objects: Vec<ObjectItem>,
pub prefixes: Vec<String>,
}
#[derive(Debug, Default, Clone)]
pub struct ListObjectVersionsInfo<ObjectItem> {
pub is_truncated: bool,
pub next_marker: Option<String>,
pub next_version_idmarker: Option<String>,
pub objects: Vec<ObjectItem>,
pub prefixes: Vec<String>,
}
#[derive(Debug)]
pub struct ObjectInfoOrErr<ObjectItem, ListError> {
pub item: Option<ObjectItem>,
pub err: Option<ListError>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HTTPRangeError {
InvalidRangeSpec(String),
@@ -386,6 +418,40 @@ mod tests {
assert!(matches!(WalkVersionsSortOrder::default(), WalkVersionsSortOrder::Ascending));
}
#[test]
fn object_list_response_contracts_default_to_empty_collections() {
let v1 = ListObjectsInfo::<()>::default();
assert!(!v1.is_truncated);
assert!(v1.next_marker.is_none());
assert!(v1.objects.is_empty());
assert!(v1.prefixes.is_empty());
let v2 = ListObjectsV2Info::<()>::default();
assert!(!v2.is_truncated);
assert!(v2.continuation_token.is_none());
assert!(v2.next_continuation_token.is_none());
assert!(v2.objects.is_empty());
assert!(v2.prefixes.is_empty());
let versions = ListObjectVersionsInfo::<()>::default();
assert!(!versions.is_truncated);
assert!(versions.next_marker.is_none());
assert!(versions.next_version_idmarker.is_none());
assert!(versions.objects.is_empty());
assert!(versions.prefixes.is_empty());
}
#[test]
fn object_info_or_err_keeps_optional_item_and_error_slots() {
let item = ObjectInfoOrErr::<usize, &str> {
item: Some(7),
err: None,
};
assert_eq!(item.item, Some(7));
assert!(item.err.is_none());
}
#[test]
fn http_range_spec_offset_length_handles_suffix_and_bounds() {
let range = HTTPRangeSpec {
+1
View File
@@ -95,6 +95,7 @@ Required `rustfs-storage-api` public re-exports:
- `pub use error::{StorageErrorCode, StorageResult};`
- `pub use multipart::{CompletePart, ListMultipartsInfo, ListPartsInfo, MultipartInfo, MultipartUploadResult, PartInfo};`
- `pub use object::{HTTPPreconditions, HTTPRangeError, HTTPRangeSpec, ObjectLockRetentionOptions};`
- `pub use object::{ListObjectVersionsInfo, ListObjectsInfo, ListObjectsV2Info, ObjectInfoOrErr};`
- `pub use object::{ObjectPreconditionError, ObjectPreconditionPart, ObjectPreconditionState};`
- `pub use object::{VersionMarker, WalkVersionsSortOrder};`
+50 -27
View File
@@ -5,18 +5,18 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
## Current Context
- Issue: [`rustfs/backlog#660`](https://github.com/rustfs/backlog/issues/660)
- Branch: `overtrue/arch-object-precondition-contracts`
- Baseline: `overtrue/arch-object-list-helper-contracts` at `65b56e1fb4fa4c65c167e2f434bf5fcb2a0b0cd4f`
over `overtrue/arch-range-contracts` at `829bc62095f0fd853747cd6d7d77f8558baf24a2`
- Branch: `overtrue/arch-list-response-contracts`
- Baseline: `main` at `80ed63484bf62b916ceda17fd76b1092261fda77`
after the object precondition contract merge.
- PR type for this branch: `api-extraction`
- Runtime behavior changes: no external behavior change expected.
- Rust code changes: move pure object HTTP precondition evaluation into
`rustfs-storage-api` as `ObjectPreconditionState`,
`ObjectPreconditionPart`, and `ObjectPreconditionError`, then keep
`ObjectOptions`, `ObjectInfo`, and ECStore error mapping in ECStore.
- CI/script changes: extend migration guards for object precondition contract
public re-exports.
- Docs changes: record the object precondition contract extraction slice.
- Rust code changes: move object list response DTO contracts into
`rustfs-storage-api` as generic `ListObjectsInfo`,
`ListObjectsV2Info`, `ListObjectVersionsInfo`, and `ObjectInfoOrErr`,
then keep ECStore's existing public names as `ObjectInfo`/`Error` aliases.
- CI/script changes: extend migration guards for list response contract public
re-exports and ECStore local-definition regressions.
- Docs changes: record the object list response contract extraction slice.
## Phase 0 Tasks
@@ -619,6 +619,29 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
checks, migration/layer guards, formatting, diff hygiene, Rust risk scan,
and required three-expert review passed.
- [x] `API-019` Move object list response contracts.
- Completed slice: move `ListObjectsInfo`, `ListObjectsV2Info`,
`ListObjectVersionsInfo`, and `ObjectInfoOrErr` from ECStore
`store_api/types.rs` into `rustfs-storage-api` as generic public
contracts, then keep ECStore's old public names as type aliases bound to
`ObjectInfo` and `Error`.
- Acceptance: `rustfs-storage-api` exports the generic list response
contracts, ECStore no longer defines local response structs for these
contracts, existing ECStore consumers keep their old import path, and
migration guards reject dropping the public storage-api re-export or
reintroducing local ECStore definitions.
- Must preserve: list v1/v2 truncation and marker fields, list-object-version
marker fields, object/prefix vectors, walk item/error channel shape, and
ECStore list/walk runtime behavior.
- Risk defense: only generic response containers cross into
`rustfs-storage-api`; ECStore keeps `ObjectInfo`, `ObjectOptions`,
`WalkOptions`, filemeta filters, object metadata adaptation, storage errors,
readers, lifecycle/replication coupling, and list/walk implementation
bodies.
- Verification: focused storage-api tests, ECStore/RustFS/downstream compile
checks, migration/layer guards, formatting, diff hygiene, Rust risk scan,
full pre-commit, and required three-expert review passed.
## Phase 8 Background Controller Tasks
- [x] `BGC-001` Inventory background services.
@@ -895,16 +918,15 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
| Expert | Status | Notes |
|---|---|---|
| Quality/architecture | passed | Pure precondition decision state and result contracts now live in `rustfs-storage-api`; ECStore still owns object metadata adaptation and storage error mapping. |
| Migration preservation | passed | Requested-part validation, ETag/date precondition priority, empty condition handling, and existing ECStore `Error` mapping are preserved. |
| Testing/verification | passed | Focused storage-api/ECStore tests, compile checks, migration/layer guards, formatting, diff hygiene, Rust risk scan, and full `make pre-commit` passed. |
| Quality/architecture | passed | Generic list response containers now live in `rustfs-storage-api`; ECStore still owns `ObjectInfo`, `Error`, walk options, and list implementation behavior. |
| Migration preservation | passed | List v1/v2 fields, version-list fields, walk item/error channel shape, and existing ECStore public import paths are preserved through aliases. |
| Testing/verification | passed | Focused storage-api tests, downstream compile checks, migration/layer guards, formatting, diff hygiene, Rust risk scan, and full `make pre-commit` passed. |
## Verification Notes
Passed before push:
- `cargo test -p rustfs-storage-api`: passed.
- `cargo test -p rustfs-ecstore precondition_check_ignores_empty_etag_conditions`: passed.
- `cargo check --tests -p rustfs-storage-api -p rustfs-ecstore -p rustfs -p rustfs-iam -p rustfs-scanner`: passed.
- `./scripts/check_architecture_migration_rules.sh`: passed.
- `./scripts/check_layer_dependencies.sh`: passed.
@@ -913,25 +935,26 @@ Passed before push:
- Rust risk scan: no new `unwrap`/`expect`, panic/todo markers, `unsafe`,
process-spawning calls, lossy casts, println/eprintln, or relaxed ordering in
added Rust lines.
- `make pre-commit`: passed; nextest reported 6164 tests passed and 111
- `make pre-commit`: passed; nextest reported 6191 tests passed and 111
skipped, and doctests passed.
Notes:
- This slice follows the range helper contract branch and keeps the old
- This slice follows the object precondition contract branch and keeps the old
aggregate facade, bucket DTO, multipart DTO, bucket operation contract, object
helper, range helper, list helper, and object precondition contract guards
active.
- The shared precondition helper contract is now owned by
`rustfs-storage-api`; ECStore keeps `ObjectOptions`, `ObjectInfo`, object
metadata adaptation, storage error mapping, readers, lifecycle/replication,
rio, filemeta, and implementation behavior.
- The slice does not alter object, list, walk, multipart, bucket, or reader
runtime behavior.
helper, range helper, list helper, object precondition, and list response
contract guards active.
- The shared list response containers are now owned by `rustfs-storage-api`;
ECStore keeps `ObjectInfo`, storage `Error`, `ObjectOptions`, `WalkOptions`,
object metadata adaptation, storage error mapping, readers,
lifecycle/replication, rio, filemeta, and implementation behavior.
- The slice does not alter object, list, walk, multipart, bucket, delete, or
reader runtime behavior.
## Handoff Notes
- Object precondition contract cleanup is stacked on the list helper contract
branch.
- Object list response contract cleanup is stacked on the object precondition
contract branch.
- After this lands, remaining storage work can continue by extracting larger
low-coupling DTO slices or by narrowing remaining operation-group consumers.
low-coupling DTO/consumer slices or by narrowing remaining operation-group
consumers.
@@ -56,6 +56,7 @@ STORE_API_MULTIPART_DTO_REEXPORTS_FILE="${TMP_DIR}/store_api_multipart_dto_reexp
STORE_API_OBJECT_HELPER_REEXPORTS_FILE="${TMP_DIR}/store_api_object_helper_reexports.txt"
STORE_API_RANGE_HELPER_REEXPORTS_FILE="${TMP_DIR}/store_api_range_helper_reexports.txt"
STORE_API_LIST_HELPER_REEXPORTS_FILE="${TMP_DIR}/store_api_list_helper_reexports.txt"
STORE_API_LIST_RESPONSE_REEXPORTS_FILE="${TMP_DIR}/store_api_list_response_reexports.txt"
awk '
/^## PR Types$/ {
@@ -196,6 +197,10 @@ require_source_line \
"crates/storage-api/src/lib.rs" \
"pub use object::{HTTPPreconditions, HTTPRangeError, HTTPRangeSpec, ObjectLockRetentionOptions};" \
"storage-api public object helper contract re-export"
require_source_line \
"crates/storage-api/src/lib.rs" \
"pub use object::{ListObjectVersionsInfo, ListObjectsInfo, ListObjectsV2Info, ObjectInfoOrErr};" \
"storage-api public list response contract re-export"
require_source_line \
"crates/storage-api/src/lib.rs" \
"pub use object::{ObjectPreconditionError, ObjectPreconditionPart, ObjectPreconditionState};" \
@@ -278,6 +283,16 @@ if [[ -s "$STORE_API_LIST_HELPER_REEXPORTS_FILE" ]]; then
report_failure "old ecstore store_api list helper path reintroduced: $(paste -sd '; ' "$STORE_API_LIST_HELPER_REEXPORTS_FILE")"
fi
(
cd "$ROOT_DIR"
rg -n --no-heading 'pub(?:\(crate\))? use rustfs_storage_api(?:::\{[^}]*\b(?:ListObjectVersionsInfo|ListObjectsInfo|ListObjectsV2Info|ObjectInfoOrErr)\b|::(?:ListObjectVersionsInfo|ListObjectsInfo|ListObjectsV2Info|ObjectInfoOrErr)\b)|pub struct (?:ListObjectVersionsInfo|ListObjectsInfo|ListObjectsV2Info|ObjectInfoOrErr)\b' \
crates/ecstore/src/store_api.rs crates/ecstore/src/store_api/types.rs || true
) >"$STORE_API_LIST_RESPONSE_REEXPORTS_FILE"
if [[ -s "$STORE_API_LIST_RESPONSE_REEXPORTS_FILE" ]]; then
report_failure "old ecstore store_api list response path reintroduced: $(paste -sd '; ' "$STORE_API_LIST_RESPONSE_REEXPORTS_FILE")"
fi
require_source_contains \
"crates/ecstore/src/store_api/traits.rs" \
"pub trait NamespaceLocking: Send + Sync + Debug + 'static" \