diff --git a/Cargo.lock b/Cargo.lock index 77a278528..645cf919d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10069,6 +10069,7 @@ dependencies = [ "serde_json", "time", "tokio", + "uuid", ] [[package]] diff --git a/crates/ecstore/src/store_api/types.rs b/crates/ecstore/src/store_api/types.rs index b2731fc8c..ca2e6ab50 100644 --- a/crates/ecstore/src/store_api/types.rs +++ b/crates/ecstore/src/store_api/types.rs @@ -1,5 +1,5 @@ use super::*; -use rustfs_storage_api::{HTTPPreconditions, ObjectLockRetentionOptions}; +use rustfs_storage_api::{HTTPPreconditions, ObjectLockRetentionOptions, VersionMarker, WalkVersionsSortOrder}; #[derive(Debug, Default, Clone)] pub struct ObjectOptions { @@ -740,12 +740,6 @@ impl ObjectInfo { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum VersionMarker { - Null, - Version(Uuid), -} - fn versions_after_marker(file_infos: &rustfs_filemeta::FileInfoVersions, marker: VersionMarker) -> &[FileInfo] { let marker_idx = match marker { VersionMarker::Null => file_infos.versions.iter().position(|version| version.version_id.is_none()), @@ -876,13 +870,6 @@ pub struct WalkOptions { pub include_free_versions: bool, // include persisted tier free-version cleanup records } -#[derive(Clone, Default, PartialEq, Eq)] -pub enum WalkVersionsSortOrder { - #[default] - Ascending, - Descending, -} - #[derive(Debug)] pub struct ObjectInfoOrErr { pub item: Option, diff --git a/crates/ecstore/src/store_list_objects.rs b/crates/ecstore/src/store_list_objects.rs index a5757f3ac..24427c955 100644 --- a/crates/ecstore/src/store_list_objects.rs +++ b/crates/ecstore/src/store_list_objects.rs @@ -23,8 +23,7 @@ use crate::error::{ }; use crate::set_disk::SetDisks; use crate::store_api::{ - ListObjectVersionsInfo, ListObjectsInfo, ObjectInfo, ObjectInfoOrErr, ObjectOperations, ObjectOptions, VersionMarker, - WalkOptions, WalkVersionsSortOrder, + ListObjectVersionsInfo, ListObjectsInfo, ObjectInfo, ObjectInfoOrErr, ObjectOperations, ObjectOptions, WalkOptions, }; use crate::store_utils::is_reserved_or_invalid_bucket; use crate::{store::ECStore, store_api::ListObjectsV2Info}; @@ -34,6 +33,7 @@ use rustfs_filemeta::{ MetaCacheEntries, MetaCacheEntriesSorted, MetaCacheEntriesSortedResult, MetaCacheEntry, MetadataResolutionParams, merge_file_meta_versions, }; +use rustfs_storage_api::{VersionMarker, WalkVersionsSortOrder}; use rustfs_utils::path::{self, SLASH_SEPARATOR, base_dir_from_prefix}; use std::collections::{HashMap, HashSet}; use std::sync::Arc; @@ -201,11 +201,7 @@ fn list_metadata_resolution_params(bucket: String, listing_quorum: usize, versio } fn parse_version_marker(marker: String) -> Result { - if marker == "null" { - Ok(VersionMarker::Null) - } else { - Ok(VersionMarker::Version(Uuid::parse_str(&marker)?)) - } + Ok(VersionMarker::parse(marker)?) } fn version_marker_for_entries( diff --git a/crates/storage-api/Cargo.toml b/crates/storage-api/Cargo.toml index 5cf495276..d02291886 100644 --- a/crates/storage-api/Cargo.toml +++ b/crates/storage-api/Cargo.toml @@ -31,6 +31,7 @@ doctest = false async-trait.workspace = true serde.workspace = true time.workspace = true +uuid.workspace = true [dev-dependencies] serde_json.workspace = true diff --git a/crates/storage-api/src/lib.rs b/crates/storage-api/src/lib.rs index 923027368..591feddd8 100644 --- a/crates/storage-api/src/lib.rs +++ b/crates/storage-api/src/lib.rs @@ -25,3 +25,4 @@ 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::{VersionMarker, WalkVersionsSortOrder}; diff --git a/crates/storage-api/src/object.rs b/crates/storage-api/src/object.rs index f053ed914..cd7757068 100644 --- a/crates/storage-api/src/object.rs +++ b/crates/storage-api/src/object.rs @@ -14,6 +14,9 @@ use std::fmt; use time::OffsetDateTime; +use uuid::Uuid; + +const NULL_VERSION_MARKER: &str = "null"; #[derive(Debug, Default, Clone)] pub struct HTTPPreconditions { @@ -40,6 +43,30 @@ pub struct ObjectLockRetentionOptions { pub bypass_governance: bool, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum VersionMarker { + Null, + Version(Uuid), +} + +impl VersionMarker { + pub fn parse(marker: impl AsRef) -> Result { + let marker = marker.as_ref(); + if marker == NULL_VERSION_MARKER { + Ok(Self::Null) + } else { + Ok(Self::Version(Uuid::parse_str(marker)?)) + } + } +} + +#[derive(Clone, Default, PartialEq, Eq)] +pub enum WalkVersionsSortOrder { + #[default] + Ascending, + Descending, +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum HTTPRangeError { InvalidRangeSpec(String), @@ -183,6 +210,22 @@ mod tests { assert!(!opts.bypass_governance); } + #[test] + fn version_marker_parses_null_and_uuid_markers() { + assert_eq!(VersionMarker::parse("null").expect("null marker should parse"), VersionMarker::Null); + + let marker = "550e8400-e29b-41d4-a716-446655440000"; + assert_eq!( + VersionMarker::parse(marker).expect("uuid marker should parse"), + VersionMarker::Version(Uuid::parse_str(marker).expect("test uuid should parse")) + ); + } + + #[test] + fn walk_versions_sort_order_defaults_to_ascending() { + assert!(matches!(WalkVersionsSortOrder::default(), WalkVersionsSortOrder::Ascending)); + } + #[test] fn http_range_spec_offset_length_handles_suffix_and_bounds() { let range = HTTPRangeSpec { diff --git a/docs/architecture/crate-boundaries.md b/docs/architecture/crate-boundaries.md index 91517831d..ba7a1bf74 100644 --- a/docs/architecture/crate-boundaries.md +++ b/docs/architecture/crate-boundaries.md @@ -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::{VersionMarker, WalkVersionsSortOrder};` ECStore must keep compile-time coverage for both `StorageAdminApi` and the separate `NamespaceLocking` operation group. diff --git a/docs/architecture/migration-progress.md b/docs/architecture/migration-progress.md index 6e596841d..fc8c7c793 100644 --- a/docs/architecture/migration-progress.md +++ b/docs/architecture/migration-progress.md @@ -5,17 +5,19 @@ 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-range-contracts` -- Baseline: `overtrue/arch-object-option-contracts` at `5328e10a0f0cdda9cc17089956e17432f2875962` +- Branch: `overtrue/arch-object-list-helper-contracts` +- Baseline: `overtrue/arch-range-contracts` at `829bc62095f0fd853747cd6d7d77f8558baf24a2` over `origin/main` at `a9aba323c6512e6b99c3137258b97b6058075ce9` - PR type for this branch: `api-extraction` - Runtime behavior changes: no external behavior change expected. -- Rust code changes: move the pure `HTTPRangeSpec` range contract and - `HTTPRangeError` into `rustfs-storage-api`, then keep ECStore object-info - adaptation at the ECStore boundary. -- CI/script changes: extend migration guards for range helper public re-exports - and reject restoring the old ECStore-owned range helper definitions. -- Docs changes: record the range contract extraction slice. +- Rust code changes: move the pure `VersionMarker` and + `WalkVersionsSortOrder` list helper contracts into `rustfs-storage-api`, + then keep ECStore list/walk implementations and filemeta adaptation at the + ECStore boundary. +- CI/script changes: extend migration guards for list helper public re-exports + and reject restoring the old ECStore-owned list helper definitions or + public re-exports. +- Docs changes: record the list helper contract extraction slice. ## Phase 0 Tasks @@ -577,6 +579,26 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block migration/layer guards, formatting, diff hygiene, Rust risk scan, and required three-expert review passed. +- [x] `API-017` Move object list helper contracts. + - Completed slice: move `VersionMarker` and `WalkVersionsSortOrder` from + ECStore `store_api/types.rs` into `rustfs-storage-api`; keep + `versions_after_marker`, `WalkOptions`, `ObjectInfo`, list result DTOs, + readers, and storage list/walk implementations in ECStore. + - Acceptance: `rustfs-storage-api` exports the list helper contracts, + in-repo production code no longer imports them from + `rustfs_ecstore::store_api`, and migration guards reject restoring old + ECStore definitions or public re-exports. + - Must preserve: list-object-versions marker parsing, null version markers, + version marker application only to the first matching entry, walk sort + default, and ECStore-owned filemeta/list implementation behavior. + - Risk defense: only pure marker/sort contracts cross into + `rustfs-storage-api`; ECStore keeps filemeta conversion, list result DTOs, + walk options with filemeta filters, readers, lifecycle/replication coupling, + and storage implementation bodies. + - Verification: focused storage-api/ECStore/RustFS/downstream compile checks, + migration/layer guards, formatting, diff hygiene, Rust risk scan, and + required three-expert review passed. + ## Phase 8 Background Controller Tasks - [x] `BGC-001` Inventory background services. @@ -853,36 +875,39 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block | Expert | Status | Notes | |---|---|---| -| Quality/architecture | passed | Pure multipart/object helper contracts now live in `rustfs-storage-api`; implementation-heavy object contracts and ECStore behavior stay in ECStore. | -| Migration preservation | passed | The slice changes helper type ownership and import paths only; multipart completion mapping, HTTP precondition values, object-lock retention fields, and storage hot paths are unchanged. | +| Quality/architecture | passed | Pure object list helper contracts now live in `rustfs-storage-api`; implementation-heavy list/walk contracts and ECStore behavior stay in ECStore. | +| Migration preservation | passed | The slice changes helper type ownership and import paths only; null version marker parsing, first-entry version marker behavior, walk sort default, and ECStore filemeta/list behavior are unchanged. | | Testing/verification | passed | Focused storage-api/ECStore/RustFS/downstream compile checks, migration/layer guards, formatting, diff hygiene, Rust risk scan, and full `make pre-commit` passed. | ## Verification Notes -Passed on `8d24d9133b31c0ca0de0fcd16ad06cd2fdc6f7ae`: +Passed before push: -- `cargo check -p rustfs-storage-api -p rustfs-ecstore -p rustfs-iam -p rustfs-scanner -p rustfs`: passed. +- `cargo test -p rustfs-storage-api`: 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. - `cargo fmt --all --check`: passed. - `git diff --check`: passed. - Rust risk scan: no new production `unwrap`/`expect`, panic/todo markers, - `unsafe`, or process-spawning calls in added Rust lines. + `unsafe`, process-spawning calls, lossy casts, println/eprintln, or relaxed + ordering in added Rust lines; new `expect` calls are test-only assertions. - `make pre-commit`: passed. Notes: -- This slice follows `rustfs/rustfs#3507` and keeps the old aggregate facade, - bucket DTO, multipart DTO, bucket operation contract, and object helper - contract guards active. -- The shared object helper contracts are now owned by `rustfs-storage-api`; - ECStore keeps implementation-heavy object, list, reader, lock, lifecycle, - replication, rio, filemeta, and storage error contracts. -- The slice does not alter object, multipart, or bucket runtime behavior. +- This slice follows the range helper contract branch and keeps the old + aggregate facade, bucket DTO, multipart DTO, bucket operation contract, object + helper, range helper, and list helper contract guards active. +- The shared list marker/sort helper contracts are now owned by + `rustfs-storage-api`; ECStore keeps implementation-heavy object/list result + DTOs, walk options with filemeta filters, readers, lifecycle/replication, rio, + filemeta, and storage error contracts. +- The slice does not alter list, walk, object, multipart, or bucket runtime + behavior. ## Handoff Notes -- Object helper contract cleanup is in progress on a branch current with - `origin/main`. +- List helper contract cleanup is stacked on the range helper contract branch. - After this lands, remaining storage work can continue by extracting larger low-coupling DTO slices or by narrowing remaining operation-group consumers. diff --git a/scripts/check_architecture_migration_rules.sh b/scripts/check_architecture_migration_rules.sh index cd869e5ac..9c05ec30a 100755 --- a/scripts/check_architecture_migration_rules.sh +++ b/scripts/check_architecture_migration_rules.sh @@ -55,6 +55,7 @@ STORE_API_BUCKET_OPERATION_HITS_FILE="${TMP_DIR}/store_api_bucket_operation_hits STORE_API_MULTIPART_DTO_REEXPORTS_FILE="${TMP_DIR}/store_api_multipart_dto_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_LIST_HELPER_REEXPORTS_FILE="${TMP_DIR}/store_api_list_helper_reexports.txt" awk ' /^## PR Types$/ { @@ -195,6 +196,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::{VersionMarker, WalkVersionsSortOrder};" \ + "storage-api public list helper contract re-export" require_source_line \ "crates/storage-api/src/lib.rs" \ "pub use error::{StorageErrorCode, StorageResult};" \ @@ -259,6 +264,16 @@ if [[ -s "$STORE_API_RANGE_HELPER_REEXPORTS_FILE" ]]; then report_failure "old ecstore store_api range helper path reintroduced: $(paste -sd '; ' "$STORE_API_RANGE_HELPER_REEXPORTS_FILE")" fi +( + cd "$ROOT_DIR" + rg -n --no-heading 'pub(?:\(crate\))? use rustfs_storage_api(?:::\{[^}]*\b(?:VersionMarker|WalkVersionsSortOrder)\b|::(?:VersionMarker|WalkVersionsSortOrder)\b)|pub enum (?:VersionMarker|WalkVersionsSortOrder)\b' \ + crates/ecstore/src/store_api.rs crates/ecstore/src/store_api/types.rs || true +) >"$STORE_API_LIST_HELPER_REEXPORTS_FILE" + +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 + require_source_contains \ "crates/ecstore/src/store_api/traits.rs" \ "pub trait NamespaceLocking: Send + Sync + Debug + 'static" \