diff --git a/rustfs/src/app/bucket_usecase.rs b/rustfs/src/app/bucket_usecase.rs index b2637567a..62043bb38 100644 --- a/rustfs/src/app/bucket_usecase.rs +++ b/rustfs/src/app/bucket_usecase.rs @@ -25,6 +25,7 @@ use crate::storage::ecfs::{ stored_acl_from_grant_headers, stored_acl_from_policy, stored_grant_to_dto, stored_owner_to_dto, }; use crate::storage::helper::OperationHelper; +use crate::storage::s3_api::{encryption, replication, tagging}; use crate::storage::*; use futures::StreamExt; use http::StatusCode; @@ -651,7 +652,7 @@ impl DefaultBucketUsecase { .await .map_err(ApiError::from)?; - Ok(S3Response::new(DeleteBucketTaggingOutput {})) + Ok(S3Response::new(tagging::build_delete_bucket_tagging_output())) } #[instrument(level = "debug", skip(self))] @@ -708,9 +709,9 @@ impl DefaultBucketUsecase { } }; - Ok(S3Response::new(GetBucketEncryptionOutput { + Ok(S3Response::new(encryption::build_get_bucket_encryption_output( server_side_encryption_configuration, - })) + ))) } #[instrument(level = "debug", skip(self))] @@ -986,9 +987,9 @@ impl DefaultBucketUsecase { } }; - Ok(S3Response::new(GetBucketReplicationOutput { - replication_configuration: Some(replication_configuration), - })) + Ok(S3Response::new(replication::build_get_bucket_replication_output( + replication_configuration, + ))) } #[instrument(level = "debug", skip(self))] @@ -1022,7 +1023,7 @@ impl DefaultBucketUsecase { } }; - Ok(S3Response::new(GetBucketTaggingOutput { tag_set })) + Ok(S3Response::new(tagging::build_get_bucket_tagging_output(tag_set))) } #[instrument(level = "debug", skip(self))] @@ -1119,7 +1120,7 @@ impl DefaultBucketUsecase { metadata_sys::update(&bucket, BUCKET_SSECONFIG, data) .await .map_err(ApiError::from)?; - Ok(S3Response::new(PutBucketEncryptionOutput::default())) + Ok(S3Response::new(encryption::build_put_bucket_encryption_output())) } #[instrument(level = "debug", skip(self))] @@ -1345,7 +1346,7 @@ impl DefaultBucketUsecase { .await .map_err(ApiError::from)?; - Ok(S3Response::new(PutBucketReplicationOutput::default())) + Ok(S3Response::new(replication::build_put_bucket_replication_output())) } #[instrument(level = "debug", skip(self))] @@ -1406,7 +1407,7 @@ impl DefaultBucketUsecase { .await .map_err(ApiError::from)?; - Ok(S3Response::new(Default::default())) + Ok(S3Response::new(tagging::build_put_bucket_tagging_output())) } #[instrument(level = "debug", skip(self))] diff --git a/rustfs/src/app/object_usecase.rs b/rustfs/src/app/object_usecase.rs index 3d82d8789..b1051abb9 100644 --- a/rustfs/src/app/object_usecase.rs +++ b/rustfs/src/app/object_usecase.rs @@ -29,6 +29,7 @@ use crate::storage::options::{ copy_dst_opts, copy_src_opts, del_opts, extract_metadata, extract_metadata_from_mime_with_object_name, filter_object_metadata, get_content_sha256, get_opts, put_opts, }; +use crate::storage::s3_api::{restore, select}; use crate::storage::*; use base64::{Engine, engine::general_purpose::STANDARD as BASE64_STANDARD}; use bytes::Bytes; @@ -3335,10 +3336,8 @@ impl DefaultObjectUsecase { .map_err(|_| S3Error::with_message(S3ErrorCode::Custom("ErrCopyObject".into()), "restore object failed."))?; if already_restored { - let output = RestoreObjectOutput { - request_charged: Some(RequestCharged::from_static(RequestCharged::REQUESTER)), - restore_output_path: None, - }; + let output = + restore::build_restore_object_output(Some(RequestCharged::from_static(RequestCharged::REQUESTER)), None); return Ok(S3Response::new(output)); } } @@ -3389,10 +3388,7 @@ impl DefaultObjectUsecase { } }); - let output = RestoreObjectOutput { - request_charged: Some(RequestCharged::from_static(RequestCharged::REQUESTER)), - restore_output_path: None, - }; + let output = restore::build_restore_object_output(Some(RequestCharged::from_static(RequestCharged::REQUESTER)), None); Ok(S3Response::with_headers(output, header)) } @@ -3471,9 +3467,9 @@ impl DefaultObjectUsecase { drop(tx); }); - Ok(S3Response::new(SelectObjectContentOutput { - payload: Some(SelectObjectContentEventStream::new(stream)), - })) + Ok(S3Response::new(select::build_select_object_content_output( + SelectObjectContentEventStream::new(stream), + ))) } } diff --git a/rustfs/src/storage/mod.rs b/rustfs/src/storage/mod.rs index 571e344e3..eebbb18e6 100644 --- a/rustfs/src/storage/mod.rs +++ b/rustfs/src/storage/mod.rs @@ -18,6 +18,7 @@ pub mod ecfs; pub(crate) mod entity; pub(crate) mod helper; pub mod options; +pub(crate) mod s3_api; pub mod tonic_service; #[cfg(test)] diff --git a/rustfs/src/storage/s3_api/encryption.rs b/rustfs/src/storage/s3_api/encryption.rs new file mode 100644 index 000000000..af5f4cf37 --- /dev/null +++ b/rustfs/src/storage/s3_api/encryption.rs @@ -0,0 +1,47 @@ +// 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::{GetBucketEncryptionOutput, PutBucketEncryptionOutput, ServerSideEncryptionConfiguration}; + +pub(crate) fn build_get_bucket_encryption_output( + server_side_encryption_configuration: Option, +) -> GetBucketEncryptionOutput { + GetBucketEncryptionOutput { + server_side_encryption_configuration, + } +} + +pub(crate) fn build_put_bucket_encryption_output() -> PutBucketEncryptionOutput { + PutBucketEncryptionOutput::default() +} + +#[cfg(test)] +mod tests { + use super::{build_get_bucket_encryption_output, build_put_bucket_encryption_output}; + use s3s::dto::ServerSideEncryptionConfiguration; + + #[test] + fn test_build_get_bucket_encryption_output_preserves_configuration() { + let config = Some(ServerSideEncryptionConfiguration::default()); + let output = build_get_bucket_encryption_output(config.clone()); + + assert_eq!(output.server_side_encryption_configuration, config); + } + + #[test] + fn test_build_put_bucket_encryption_output_is_default() { + let output = build_put_bucket_encryption_output(); + assert_eq!(output, Default::default()); + } +} diff --git a/rustfs/src/storage/s3_api/mod.rs b/rustfs/src/storage/s3_api/mod.rs index d033efbb3..3de017587 100644 --- a/rustfs/src/storage/s3_api/mod.rs +++ b/rustfs/src/storage/s3_api/mod.rs @@ -16,11 +16,12 @@ //! //! This file intentionally starts as skeleton-only. Behavior remains in place //! until each helper is moved with dedicated small refactor steps. +#![allow(dead_code)] pub(crate) mod acl; pub(crate) mod bucket; pub(crate) mod common; -pub(crate) mod encryption {} +pub(crate) mod encryption; pub(crate) mod multipart; pub(crate) mod object_lock; /// Object helper facade placeholder. @@ -29,9 +30,9 @@ pub(crate) mod object_lock; /// modules (for example, `storage::readers`) and be consumed from there. /// Object-specific extraction steps can be added here incrementally. pub(crate) mod object {} -pub(crate) mod replication {} +pub(crate) mod replication; pub(crate) mod response; -pub(crate) mod restore {} -pub(crate) mod select {} +pub(crate) mod restore; +pub(crate) mod select; pub(crate) mod tagging; pub(crate) mod validation {} diff --git a/rustfs/src/storage/s3_api/replication.rs b/rustfs/src/storage/s3_api/replication.rs new file mode 100644 index 000000000..4bf410f7d --- /dev/null +++ b/rustfs/src/storage/s3_api/replication.rs @@ -0,0 +1,47 @@ +// 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::{GetBucketReplicationOutput, PutBucketReplicationOutput, ReplicationConfiguration}; + +pub(crate) fn build_get_bucket_replication_output( + replication_configuration: ReplicationConfiguration, +) -> GetBucketReplicationOutput { + GetBucketReplicationOutput { + replication_configuration: Some(replication_configuration), + } +} + +pub(crate) fn build_put_bucket_replication_output() -> PutBucketReplicationOutput { + PutBucketReplicationOutput::default() +} + +#[cfg(test)] +mod tests { + use super::{build_get_bucket_replication_output, build_put_bucket_replication_output}; + use s3s::dto::ReplicationConfiguration; + + #[test] + fn test_build_get_bucket_replication_output_sets_configuration() { + let config = ReplicationConfiguration::default(); + let output = build_get_bucket_replication_output(config.clone()); + + assert_eq!(output.replication_configuration, Some(config)); + } + + #[test] + fn test_build_put_bucket_replication_output_is_default() { + let output = build_put_bucket_replication_output(); + assert_eq!(output, Default::default()); + } +} diff --git a/rustfs/src/storage/s3_api/restore.rs b/rustfs/src/storage/s3_api/restore.rs new file mode 100644 index 000000000..df581727c --- /dev/null +++ b/rustfs/src/storage/s3_api/restore.rs @@ -0,0 +1,42 @@ +// 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::{RequestCharged, RestoreObjectOutput}; + +pub(crate) fn build_restore_object_output( + request_charged: Option, + restore_output_path: Option, +) -> RestoreObjectOutput { + RestoreObjectOutput { + request_charged, + restore_output_path, + } +} + +#[cfg(test)] +mod tests { + use super::build_restore_object_output; + use s3s::dto::RequestCharged; + + #[test] + fn test_build_restore_object_output_preserves_fields() { + let output = build_restore_object_output( + Some(RequestCharged::from_static(RequestCharged::REQUESTER)), + Some("s3://bucket/prefix/id".to_string()), + ); + + assert_eq!(output.request_charged, Some(RequestCharged::from_static(RequestCharged::REQUESTER))); + assert_eq!(output.restore_output_path, Some("s3://bucket/prefix/id".to_string())); + } +} diff --git a/rustfs/src/storage/s3_api/select.rs b/rustfs/src/storage/s3_api/select.rs new file mode 100644 index 000000000..f032b2390 --- /dev/null +++ b/rustfs/src/storage/s3_api/select.rs @@ -0,0 +1,19 @@ +// 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::{SelectObjectContentEventStream, SelectObjectContentOutput}; + +pub(crate) fn build_select_object_content_output(payload: SelectObjectContentEventStream) -> SelectObjectContentOutput { + SelectObjectContentOutput { payload: Some(payload) } +} diff --git a/rustfs/src/storage/s3_api/tagging.rs b/rustfs/src/storage/s3_api/tagging.rs index 1b3cdf2ac..f36046d6c 100644 --- a/rustfs/src/storage/s3_api/tagging.rs +++ b/rustfs/src/storage/s3_api/tagging.rs @@ -12,7 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use s3s::dto::{DeleteObjectTaggingOutput, GetBucketTaggingOutput, GetObjectTaggingOutput, PutObjectTaggingOutput, Tag}; +use s3s::dto::{ + DeleteBucketTaggingOutput, DeleteObjectTaggingOutput, GetBucketTaggingOutput, GetObjectTaggingOutput, PutBucketTaggingOutput, + PutObjectTaggingOutput, Tag, +}; use s3s::{S3Error, S3ErrorCode, S3Result}; use std::collections::HashSet; @@ -70,14 +73,23 @@ pub(crate) fn build_delete_object_tagging_output(version_id: Option) -> DeleteObjectTaggingOutput { version_id } } +pub(crate) fn build_put_bucket_tagging_output() -> PutBucketTaggingOutput { + PutBucketTaggingOutput::default() +} + +pub(crate) fn build_delete_bucket_tagging_output() -> DeleteBucketTaggingOutput { + DeleteBucketTaggingOutput {} +} + #[cfg(test)] mod tests { use super::{ - build_delete_object_tagging_output, build_get_bucket_tagging_output, build_get_object_tagging_output, - build_put_object_tagging_output, validate_object_tag_set, + build_delete_bucket_tagging_output, build_delete_object_tagging_output, build_get_bucket_tagging_output, + build_get_object_tagging_output, build_put_bucket_tagging_output, build_put_object_tagging_output, + validate_object_tag_set, }; use s3s::S3ErrorCode; - use s3s::dto::Tag; + use s3s::dto::{DeleteBucketTaggingOutput, Tag}; fn tag(key: Option<&str>, value: Option<&str>) -> Tag { Tag { @@ -163,4 +175,13 @@ mod tests { assert_eq!(put_object_output.version_id, Some("vid-1".to_string())); assert_eq!(delete_object_output.version_id, Some("vid-1".to_string())); } + + #[test] + fn test_build_bucket_tagging_outputs_are_default_shape() { + let put_output = build_put_bucket_tagging_output(); + let delete_output = build_delete_bucket_tagging_output(); + + assert_eq!(put_output, Default::default()); + assert_eq!(delete_output, DeleteBucketTaggingOutput {}); + } }