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, 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)] #[derive(Debug, Default, Clone)]
pub struct ObjectOptions { pub struct ObjectOptions {
// Use the maximum parity (N/2), used when saving server configuration files // 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) .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)] #[derive(Debug, Default, Clone)]
pub struct ObjectToDelete { pub struct ObjectToDelete {
pub object_name: String, 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; type WalkFilter = fn(&FileInfo) -> bool;
#[derive(Clone, Default)] #[derive(Clone, Default)]
@@ -827,12 +779,6 @@ pub struct WalkOptions {
pub include_free_versions: bool, // include persisted tier free-version cleanup records 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
+1
View File
@@ -25,5 +25,6 @@ pub use bucket::{BucketInfo, BucketOperations, BucketOptions, DeleteBucketOption
pub use error::{StorageErrorCode, StorageResult}; pub use error::{StorageErrorCode, StorageResult};
pub use multipart::{CompletePart, ListMultipartsInfo, ListPartsInfo, MultipartInfo, MultipartUploadResult, PartInfo}; pub use multipart::{CompletePart, ListMultipartsInfo, ListPartsInfo, MultipartInfo, MultipartUploadResult, PartInfo};
pub use object::{HTTPPreconditions, HTTPRangeError, HTTPRangeSpec, ObjectLockRetentionOptions}; pub use object::{HTTPPreconditions, HTTPRangeError, HTTPRangeSpec, ObjectLockRetentionOptions};
pub use object::{ListObjectVersionsInfo, ListObjectsInfo, ListObjectsV2Info, ObjectInfoOrErr};
pub use object::{ObjectPreconditionError, ObjectPreconditionPart, ObjectPreconditionState}; pub use object::{ObjectPreconditionError, ObjectPreconditionPart, ObjectPreconditionState};
pub use object::{VersionMarker, WalkVersionsSortOrder}; pub use object::{VersionMarker, WalkVersionsSortOrder};
+66
View File
@@ -154,6 +154,38 @@ pub enum WalkVersionsSortOrder {
Descending, 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)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum HTTPRangeError { pub enum HTTPRangeError {
InvalidRangeSpec(String), InvalidRangeSpec(String),
@@ -386,6 +418,40 @@ mod tests {
assert!(matches!(WalkVersionsSortOrder::default(), WalkVersionsSortOrder::Ascending)); 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] #[test]
fn http_range_spec_offset_length_handles_suffix_and_bounds() { fn http_range_spec_offset_length_handles_suffix_and_bounds() {
let range = HTTPRangeSpec { let range = HTTPRangeSpec {
+1
View File
@@ -95,6 +95,7 @@ Required `rustfs-storage-api` public re-exports:
- `pub use error::{StorageErrorCode, StorageResult};` - `pub use error::{StorageErrorCode, StorageResult};`
- `pub use multipart::{CompletePart, ListMultipartsInfo, ListPartsInfo, MultipartInfo, MultipartUploadResult, PartInfo};` - `pub use multipart::{CompletePart, ListMultipartsInfo, ListPartsInfo, MultipartInfo, MultipartUploadResult, PartInfo};`
- `pub use object::{HTTPPreconditions, HTTPRangeError, HTTPRangeSpec, ObjectLockRetentionOptions};` - `pub use object::{HTTPPreconditions, HTTPRangeError, HTTPRangeSpec, ObjectLockRetentionOptions};`
- `pub use object::{ListObjectVersionsInfo, ListObjectsInfo, ListObjectsV2Info, ObjectInfoOrErr};`
- `pub use object::{ObjectPreconditionError, ObjectPreconditionPart, ObjectPreconditionState};` - `pub use object::{ObjectPreconditionError, ObjectPreconditionPart, ObjectPreconditionState};`
- `pub use object::{VersionMarker, WalkVersionsSortOrder};` - `pub use object::{VersionMarker, WalkVersionsSortOrder};`
+50 -27
View File
@@ -5,18 +5,18 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
## Current Context ## Current Context
- Issue: [`rustfs/backlog#660`](https://github.com/rustfs/backlog/issues/660) - Issue: [`rustfs/backlog#660`](https://github.com/rustfs/backlog/issues/660)
- Branch: `overtrue/arch-object-precondition-contracts` - Branch: `overtrue/arch-list-response-contracts`
- Baseline: `overtrue/arch-object-list-helper-contracts` at `65b56e1fb4fa4c65c167e2f434bf5fcb2a0b0cd4f` - Baseline: `main` at `80ed63484bf62b916ceda17fd76b1092261fda77`
over `overtrue/arch-range-contracts` at `829bc62095f0fd853747cd6d7d77f8558baf24a2` after the object precondition contract merge.
- PR type for this branch: `api-extraction` - PR type for this branch: `api-extraction`
- Runtime behavior changes: no external behavior change expected. - Runtime behavior changes: no external behavior change expected.
- Rust code changes: move pure object HTTP precondition evaluation into - Rust code changes: move object list response DTO contracts into
`rustfs-storage-api` as `ObjectPreconditionState`, `rustfs-storage-api` as generic `ListObjectsInfo`,
`ObjectPreconditionPart`, and `ObjectPreconditionError`, then keep `ListObjectsV2Info`, `ListObjectVersionsInfo`, and `ObjectInfoOrErr`,
`ObjectOptions`, `ObjectInfo`, and ECStore error mapping in ECStore. then keep ECStore's existing public names as `ObjectInfo`/`Error` aliases.
- CI/script changes: extend migration guards for object precondition contract - CI/script changes: extend migration guards for list response contract public
public re-exports. re-exports and ECStore local-definition regressions.
- Docs changes: record the object precondition contract extraction slice. - Docs changes: record the object list response contract extraction slice.
## Phase 0 Tasks ## 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, checks, migration/layer guards, formatting, diff hygiene, Rust risk scan,
and required three-expert review passed. 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 ## Phase 8 Background Controller Tasks
- [x] `BGC-001` Inventory background services. - [x] `BGC-001` Inventory background services.
@@ -895,16 +918,15 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
| Expert | Status | Notes | | 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. | | 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 | Requested-part validation, ETag/date precondition priority, empty condition handling, and existing ECStore `Error` mapping are preserved. | | 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/ECStore tests, compile checks, migration/layer guards, formatting, diff hygiene, Rust risk scan, and full `make pre-commit` passed. | | 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 ## Verification Notes
Passed before push: Passed before push:
- `cargo test -p rustfs-storage-api`: passed. - `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. - `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_architecture_migration_rules.sh`: passed.
- `./scripts/check_layer_dependencies.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`, - Rust risk scan: no new `unwrap`/`expect`, panic/todo markers, `unsafe`,
process-spawning calls, lossy casts, println/eprintln, or relaxed ordering in process-spawning calls, lossy casts, println/eprintln, or relaxed ordering in
added Rust lines. 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. skipped, and doctests passed.
Notes: 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 aggregate facade, bucket DTO, multipart DTO, bucket operation contract, object
helper, range helper, list helper, and object precondition contract guards helper, range helper, list helper, object precondition, and list response
active. contract guards active.
- The shared precondition helper contract is now owned by - The shared list response containers are now owned by `rustfs-storage-api`;
`rustfs-storage-api`; ECStore keeps `ObjectOptions`, `ObjectInfo`, object ECStore keeps `ObjectInfo`, storage `Error`, `ObjectOptions`, `WalkOptions`,
metadata adaptation, storage error mapping, readers, lifecycle/replication, object metadata adaptation, storage error mapping, readers,
rio, filemeta, and implementation behavior. lifecycle/replication, rio, filemeta, and implementation behavior.
- The slice does not alter object, list, walk, multipart, bucket, or reader - The slice does not alter object, list, walk, multipart, bucket, delete, or
runtime behavior. reader runtime behavior.
## Handoff Notes ## Handoff Notes
- Object precondition contract cleanup is stacked on the list helper contract - Object list response contract cleanup is stacked on the object precondition
branch. contract branch.
- After this lands, remaining storage work can continue by extracting larger - 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_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_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_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 ' awk '
/^## PR Types$/ { /^## PR Types$/ {
@@ -196,6 +197,10 @@ require_source_line \
"crates/storage-api/src/lib.rs" \ "crates/storage-api/src/lib.rs" \
"pub use object::{HTTPPreconditions, HTTPRangeError, HTTPRangeSpec, ObjectLockRetentionOptions};" \ "pub use object::{HTTPPreconditions, HTTPRangeError, HTTPRangeSpec, ObjectLockRetentionOptions};" \
"storage-api public object helper contract re-export" "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 \ require_source_line \
"crates/storage-api/src/lib.rs" \ "crates/storage-api/src/lib.rs" \
"pub use object::{ObjectPreconditionError, ObjectPreconditionPart, ObjectPreconditionState};" \ "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")" report_failure "old ecstore store_api list helper path reintroduced: $(paste -sd '; ' "$STORE_API_LIST_HELPER_REEXPORTS_FILE")"
fi 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 \ require_source_contains \
"crates/ecstore/src/store_api/traits.rs" \ "crates/ecstore/src/store_api/traits.rs" \
"pub trait NamespaceLocking: Send + Sync + Debug + 'static" \ "pub trait NamespaceLocking: Send + Sync + Debug + 'static" \