mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-30 00:47:13 +00:00
8f0d4a20d1
The storage engine embedded a ~8.4K-line hand-written S3 HTTP client under crates/ecstore/src/client (rustfs/backlog#1842). That client is a legitimate engine capability — it consumes remote S3-compatible endpoints for ILM tier warm backends and transition targets — but it was misfiled inside the engine, dragging s3s/hyper wire types into ecstore and blocking ARCHITECTURE.md invariant 4. This PR is the pure-move step: 21 modules move verbatim to the new crates/s3-client crate (rustfs-s3-client), and crates/ecstore/src/client/mod.rs becomes a re-export shim so every in-crate crate::client:: path keeps working. The two server-side modules that were historically misfiled under client/ — object_api_utils.rs and object_handlers_common.rs — stay in ecstore. Three reverse dependencies from the client into engine internals are severed so the move can be pure: - transition_api::ReaderImpl::ObjectBody held ecstore's GetObjectReader; the client only ever reads the body, so the variant now holds an ObjectReader newtype over Box<dyn AsyncRead + Send + Sync + Unpin> with the same read_all() surface. The single production construction site (set_disk transition upload) and the two engine-side consumers were adjusted. - api_list/api_remove used ecstore's storage_api_contracts / object_api types; api_list now imports BucketInfo from rustfs-storage-api directly, and api_remove uses the client's own transition_api::ObjectInfo (only .name/.version_id were read; the error-path bucket name is now threaded as a parameter instead of read from the deleted objects). - the api_put_object_streaming regression tests built a GetObjectReader by hand; they now wrap the duplex stream in ObjectReader::new. Guard updates: the s3s footprint ratchet gains an ecstore-scoped counter (42 files, shrink-only, per rustfs/backlog#1842), the ecstore module-lint-blanket register follows the moved files into crates/s3-client so the blanket ratchet keeps covering them, the logging guardrail path pin follows transition_api.rs, and the ::other(format!) baseline is regenerated (moved call sites left ecstore). Verification: cargo check -p rustfs-s3-client -p rustfs-ecstore; cargo nextest run -p rustfs-s3-client (43 passed) and -p rustfs-ecstore (4515/4523; the 8 failures reproduce identically on pristine origin/main on the same machine); cargo clippy --all-targets; scripts/check_layer_dependencies.sh, check_architecture_migration_rules.sh, check_s3s_footprint.sh, check_logging_guardrails.sh, check_error_other_format_ratchet.sh, check_doc_paths.sh, check_ci_paths_sync.sh all pass.
106 lines
3.2 KiB
Rust
106 lines
3.2 KiB
Rust
// 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 std::error::Error as StdError;
|
|
use std::fmt::{Display, Formatter};
|
|
use std::io::{Error, ErrorKind};
|
|
|
|
pub const SIGNER_HEADER_ERROR_MARKER: &str = "rustfs_signer_header_error";
|
|
|
|
#[derive(Debug)]
|
|
struct SignerHeaderError {
|
|
scope: String,
|
|
header_name: String,
|
|
}
|
|
|
|
impl SignerHeaderError {
|
|
fn new(scope: &str, header_name: &str) -> Self {
|
|
Self {
|
|
scope: scope.to_string(),
|
|
header_name: header_name.to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Display for SignerHeaderError {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
write!(
|
|
f,
|
|
"{}: invalid UTF-8 header value for `{}` [{}]",
|
|
self.scope, self.header_name, SIGNER_HEADER_ERROR_MARKER
|
|
)
|
|
}
|
|
}
|
|
|
|
impl StdError for SignerHeaderError {}
|
|
|
|
pub fn invalid_utf8_header_error(scope: &str, header_name: &str) -> Error {
|
|
Error::new(ErrorKind::InvalidInput, SignerHeaderError::new(scope, header_name))
|
|
}
|
|
|
|
pub fn signer_error_to_io_error(scope: &str, error: rustfs_signer::SignV4Error) -> Error {
|
|
match error {
|
|
rustfs_signer::SignV4Error::InvalidHeaderValue { name } => invalid_utf8_header_error(scope, &name),
|
|
other => Error::other(format!("{scope}: {other}")),
|
|
}
|
|
}
|
|
|
|
pub fn error_chain_contains_signer_header_marker(err: &(dyn StdError + 'static)) -> bool {
|
|
let mut current = Some(err);
|
|
while let Some(source) = current {
|
|
if source.downcast_ref::<SignerHeaderError>().is_some() {
|
|
return true;
|
|
}
|
|
|
|
if source.to_string().contains(SIGNER_HEADER_ERROR_MARKER) {
|
|
return true;
|
|
}
|
|
|
|
current = source.source();
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::{error_chain_contains_signer_header_marker, invalid_utf8_header_error, signer_error_to_io_error};
|
|
|
|
#[test]
|
|
fn invalid_utf8_header_error_is_detected_through_error_chain() {
|
|
let err = invalid_utf8_header_error("failed to sign request", "x-amz-meta-invalid");
|
|
|
|
assert!(error_chain_contains_signer_header_marker(&err));
|
|
}
|
|
|
|
#[test]
|
|
fn mapped_signer_header_error_is_detected_through_error_chain() {
|
|
let err = signer_error_to_io_error(
|
|
"failed to sign request",
|
|
rustfs_signer::SignV4Error::InvalidHeaderValue {
|
|
name: "x-amz-meta-invalid".to_string(),
|
|
},
|
|
);
|
|
|
|
assert!(error_chain_contains_signer_header_marker(&err));
|
|
}
|
|
|
|
#[test]
|
|
fn generic_io_errors_do_not_match_signer_header_marker() {
|
|
let err = std::io::Error::other("unrelated failure");
|
|
|
|
assert!(!error_chain_contains_signer_header_marker(&err));
|
|
}
|
|
}
|