diff --git a/rustfs/src/storage/ecfs.rs b/rustfs/src/storage/ecfs.rs index 132bfd26a..d15a58a63 100644 --- a/rustfs/src/storage/ecfs.rs +++ b/rustfs/src/storage/ecfs.rs @@ -31,6 +31,10 @@ use crate::storage::s3_api::bucket::{ use crate::storage::s3_api::multipart::{ build_list_multipart_uploads_output, build_list_parts_output, parse_list_multipart_uploads_params, parse_list_parts_params, }; +use crate::storage::s3_api::object_lock::{ + build_get_object_legal_hold_output, build_get_object_lock_configuration_output, build_get_object_retention_output, + build_put_object_legal_hold_output, build_put_object_retention_output, +}; use crate::storage::s3_api::response::{ access_denied_error, map_abort_multipart_upload_error, not_initialized_error, s3_response, }; @@ -2978,22 +2982,12 @@ impl S3 for FS { s3_error!(InternalError, "{}", e.to_string()) })?; - let legal_hold = object_info + let legal_hold_status = object_info .user_defined .get(AMZ_OBJECT_LOCK_LEGAL_HOLD_LOWER) .map(|v| v.as_str().to_string()); - let status = if let Some(v) = legal_hold { - v - } else { - ObjectLockLegalHoldStatus::OFF.to_string() - }; - - let output = GetObjectLegalHoldOutput { - legal_hold: Some(ObjectLockLegalHold { - status: Some(ObjectLockLegalHoldStatus::from(status)), - }), - }; + let output = build_get_object_legal_hold_output(legal_hold_status); let version_id = req.input.version_id.clone().unwrap_or_else(|| Uuid::new_v4().to_string()); helper = helper.object(object_info).version_id(version_id); @@ -3029,9 +3023,7 @@ impl S3 for FS { // warn!("object_lock_configuration {:?}", &object_lock_configuration); - Ok(s3_response(GetObjectLockConfigurationOutput { - object_lock_configuration, - })) + Ok(s3_response(build_get_object_lock_configuration_output(object_lock_configuration))) } async fn get_object_retention( @@ -3070,9 +3062,7 @@ impl S3 for FS { .and_then(|v| OffsetDateTime::parse(v.as_str(), &Rfc3339).ok()) .map(Timestamp::from); - let output = GetObjectRetentionOutput { - retention: Some(ObjectLockRetention { mode, retain_until_date }), - }; + let output = build_get_object_retention_output(mode, retain_until_date); let version_id = req.input.version_id.clone().unwrap_or_default(); helper = helper.object(object_info).version_id(version_id); @@ -4118,9 +4108,7 @@ impl S3 for FS { s3_error!(InternalError, "{}", e.to_string()) })?; - let output = PutObjectLegalHoldOutput { - request_charged: Some(RequestCharged::from_static(RequestCharged::REQUESTER)), - }; + let output = build_put_object_legal_hold_output(); let version_id = req.input.version_id.clone().unwrap_or_default(); helper = helper.object(info).version_id(version_id); @@ -4259,9 +4247,7 @@ impl S3 for FS { s3_error!(InternalError, "{}", e.to_string()) })?; - let output = PutObjectRetentionOutput { - request_charged: Some(RequestCharged::from_static(RequestCharged::REQUESTER)), - }; + let output = build_put_object_retention_output(); let version_id = req.input.version_id.clone().unwrap_or_else(|| Uuid::new_v4().to_string()); helper = helper.object(object_info).version_id(version_id); diff --git a/rustfs/src/storage/s3_api/mod.rs b/rustfs/src/storage/s3_api/mod.rs index f529f2ea1..d033efbb3 100644 --- a/rustfs/src/storage/s3_api/mod.rs +++ b/rustfs/src/storage/s3_api/mod.rs @@ -22,6 +22,7 @@ pub(crate) mod bucket; pub(crate) mod common; pub(crate) mod encryption {} pub(crate) mod multipart; +pub(crate) mod object_lock; /// Object helper facade placeholder. /// /// Read-path helpers shared across storage components should live in neutral diff --git a/rustfs/src/storage/s3_api/object_lock.rs b/rustfs/src/storage/s3_api/object_lock.rs new file mode 100644 index 000000000..a997feb80 --- /dev/null +++ b/rustfs/src/storage/s3_api/object_lock.rs @@ -0,0 +1,130 @@ +// 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::{ + GetObjectLegalHoldOutput, GetObjectLockConfigurationOutput, GetObjectRetentionOutput, ObjectLockConfiguration, + ObjectLockLegalHold, ObjectLockLegalHoldStatus, ObjectLockRetention, ObjectLockRetentionMode, PutObjectLegalHoldOutput, + PutObjectRetentionOutput, RequestCharged, Timestamp, +}; + +pub(crate) fn build_get_object_legal_hold_output(legal_hold_status: Option) -> GetObjectLegalHoldOutput { + let status = legal_hold_status.unwrap_or_else(|| ObjectLockLegalHoldStatus::OFF.to_string()); + GetObjectLegalHoldOutput { + legal_hold: Some(ObjectLockLegalHold { + status: Some(ObjectLockLegalHoldStatus::from(status)), + }), + } +} + +pub(crate) fn build_get_object_lock_configuration_output( + object_lock_configuration: Option, +) -> GetObjectLockConfigurationOutput { + GetObjectLockConfigurationOutput { + object_lock_configuration, + } +} + +pub(crate) fn build_get_object_retention_output( + mode: Option, + retain_until_date: Option, +) -> GetObjectRetentionOutput { + GetObjectRetentionOutput { + retention: Some(ObjectLockRetention { mode, retain_until_date }), + } +} + +pub(crate) fn build_put_object_legal_hold_output() -> PutObjectLegalHoldOutput { + PutObjectLegalHoldOutput { + request_charged: Some(RequestCharged::from_static(RequestCharged::REQUESTER)), + } +} + +pub(crate) fn build_put_object_retention_output() -> PutObjectRetentionOutput { + PutObjectRetentionOutput { + request_charged: Some(RequestCharged::from_static(RequestCharged::REQUESTER)), + } +} + +#[cfg(test)] +mod tests { + use super::{ + build_get_object_legal_hold_output, build_get_object_lock_configuration_output, build_get_object_retention_output, + build_put_object_legal_hold_output, build_put_object_retention_output, + }; + use s3s::dto::{ + ObjectLockConfiguration, ObjectLockEnabled, ObjectLockLegalHoldStatus, ObjectLockRetentionMode, RequestCharged, + }; + use time::OffsetDateTime; + + #[test] + fn test_build_get_object_legal_hold_output_defaults_to_off_when_missing() { + let output = build_get_object_legal_hold_output(None); + let status = output + .legal_hold + .as_ref() + .and_then(|hold| hold.status.as_ref()) + .map(ObjectLockLegalHoldStatus::as_str); + assert_eq!(status, Some(ObjectLockLegalHoldStatus::OFF)); + } + + #[test] + fn test_build_get_object_legal_hold_output_uses_input_status() { + let output = build_get_object_legal_hold_output(Some(ObjectLockLegalHoldStatus::ON.to_string())); + let status = output + .legal_hold + .as_ref() + .and_then(|hold| hold.status.as_ref()) + .map(ObjectLockLegalHoldStatus::as_str); + assert_eq!(status, Some(ObjectLockLegalHoldStatus::ON)); + } + + #[test] + fn test_build_get_object_lock_configuration_output_preserves_field() { + let cfg = ObjectLockConfiguration { + object_lock_enabled: Some(ObjectLockEnabled::from_static(ObjectLockEnabled::ENABLED)), + ..Default::default() + }; + let output = build_get_object_lock_configuration_output(Some(cfg.clone())); + assert_eq!(output.object_lock_configuration, Some(cfg)); + } + + #[test] + fn test_build_get_object_retention_output_preserves_fields() { + let mode = Some(ObjectLockRetentionMode::from_static(ObjectLockRetentionMode::GOVERNANCE)); + let retain_until_date = Some(OffsetDateTime::UNIX_EPOCH.into()); + let output = build_get_object_retention_output(mode.clone(), retain_until_date.clone()); + + let retention = output.retention.expect("retention should be present"); + assert_eq!(retention.mode, mode); + assert_eq!(retention.retain_until_date, retain_until_date); + } + + #[test] + fn test_build_put_object_legal_hold_output_sets_request_charged() { + let output = build_put_object_legal_hold_output(); + assert_eq!( + output.request_charged.as_ref().map(RequestCharged::as_str), + Some(RequestCharged::REQUESTER) + ); + } + + #[test] + fn test_build_put_object_retention_output_sets_request_charged() { + let output = build_put_object_retention_output(); + assert_eq!( + output.request_charged.as_ref().map(RequestCharged::as_str), + Some(RequestCharged::REQUESTER) + ); + } +}