mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-30 08:49:26 +00:00
feat: object retention (#1589)
This commit is contained in:
@@ -31,63 +31,206 @@ pub fn utc_now_ntp() -> OffsetDateTime {
|
||||
OffsetDateTime::now_utc()
|
||||
}
|
||||
|
||||
pub fn get_object_retention_meta(meta: HashMap<String, String>) -> ObjectLockRetention {
|
||||
let mut retain_until_date: Date = Date::from(OffsetDateTime::UNIX_EPOCH);
|
||||
pub fn get_object_retention_meta(meta: &HashMap<String, String>) -> ObjectLockRetention {
|
||||
// Note: X_AMZ_OBJECT_LOCK_MODE.as_str() is already lowercase ("x-amz-object-lock-mode")
|
||||
let mode_str = meta.get(X_AMZ_OBJECT_LOCK_MODE.as_str());
|
||||
|
||||
let mut mode_str = meta.get(X_AMZ_OBJECT_LOCK_MODE.as_str().to_lowercase().as_str());
|
||||
if mode_str.is_none() {
|
||||
mode_str = Some(&meta[X_AMZ_OBJECT_LOCK_MODE.as_str()]);
|
||||
}
|
||||
let mode = if let Some(mode_str) = mode_str {
|
||||
parse_ret_mode(mode_str.as_str())
|
||||
} else {
|
||||
let Some(mode_str) = mode_str else {
|
||||
return ObjectLockRetention {
|
||||
mode: None,
|
||||
retain_until_date: None,
|
||||
};
|
||||
};
|
||||
|
||||
let mut till_str = meta.get(X_AMZ_OBJECT_LOCK_RETAIN_UNTIL_DATE.as_str().to_lowercase().as_str());
|
||||
if till_str.is_none() {
|
||||
till_str = Some(&meta[X_AMZ_OBJECT_LOCK_RETAIN_UNTIL_DATE.as_str()]);
|
||||
}
|
||||
if let Some(till_str) = till_str {
|
||||
let t = OffsetDateTime::parse(till_str, &format_description::well_known::Iso8601::DEFAULT);
|
||||
if let Ok(parsed_time) = t {
|
||||
retain_until_date = Date::from(parsed_time);
|
||||
}
|
||||
}
|
||||
// If mode is invalid, return empty retention (don't panic)
|
||||
let Some(mode) = parse_ret_mode(mode_str.as_str()) else {
|
||||
return ObjectLockRetention {
|
||||
mode: None,
|
||||
retain_until_date: None,
|
||||
};
|
||||
};
|
||||
|
||||
let till_str = meta.get(X_AMZ_OBJECT_LOCK_RETAIN_UNTIL_DATE.as_str());
|
||||
|
||||
let retain_until_date = till_str
|
||||
.and_then(|s| OffsetDateTime::parse(s, &format_description::well_known::Iso8601::DEFAULT).ok())
|
||||
.map(Date::from);
|
||||
|
||||
ObjectLockRetention {
|
||||
mode: Some(mode),
|
||||
retain_until_date: Some(retain_until_date),
|
||||
retain_until_date,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_object_legalhold_meta(meta: HashMap<String, String>) -> ObjectLockLegalHold {
|
||||
let mut hold_str = meta.get(X_AMZ_OBJECT_LOCK_LEGAL_HOLD.as_str().to_lowercase().as_str());
|
||||
if hold_str.is_none() {
|
||||
hold_str = Some(&meta[X_AMZ_OBJECT_LOCK_LEGAL_HOLD.as_str()]);
|
||||
pub fn get_object_legalhold_meta(meta: &HashMap<String, String>) -> ObjectLockLegalHold {
|
||||
// Note: X_AMZ_OBJECT_LOCK_LEGAL_HOLD.as_str() is already lowercase
|
||||
let hold_str = meta.get(X_AMZ_OBJECT_LOCK_LEGAL_HOLD.as_str());
|
||||
|
||||
match hold_str.and_then(|s| parse_legalhold_status(s)) {
|
||||
Some(status) => ObjectLockLegalHold { status: Some(status) },
|
||||
None => ObjectLockLegalHold { status: None },
|
||||
}
|
||||
if let Some(hold_str) = hold_str {
|
||||
return ObjectLockLegalHold {
|
||||
status: Some(parse_legalhold_status(hold_str)),
|
||||
};
|
||||
}
|
||||
ObjectLockLegalHold { status: None }
|
||||
}
|
||||
|
||||
pub fn parse_ret_mode(mode_str: &str) -> ObjectLockRetentionMode {
|
||||
/// Parse retention mode string into ObjectLockRetentionMode.
|
||||
/// Returns None for invalid/unknown mode strings instead of panicking.
|
||||
pub fn parse_ret_mode(mode_str: &str) -> Option<ObjectLockRetentionMode> {
|
||||
match mode_str.to_uppercase().as_str() {
|
||||
"GOVERNANCE" => ObjectLockRetentionMode::from_static(ObjectLockRetentionMode::GOVERNANCE),
|
||||
"COMPLIANCE" => ObjectLockRetentionMode::from_static(ObjectLockRetentionMode::COMPLIANCE),
|
||||
_ => unreachable!(),
|
||||
"GOVERNANCE" => Some(ObjectLockRetentionMode::from_static(ObjectLockRetentionMode::GOVERNANCE)),
|
||||
"COMPLIANCE" => Some(ObjectLockRetentionMode::from_static(ObjectLockRetentionMode::COMPLIANCE)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_legalhold_status(hold_str: &str) -> ObjectLockLegalHoldStatus {
|
||||
match hold_str {
|
||||
"ON" => ObjectLockLegalHoldStatus::from_static(ObjectLockLegalHoldStatus::ON),
|
||||
"OFF" => ObjectLockLegalHoldStatus::from_static(ObjectLockLegalHoldStatus::OFF),
|
||||
_ => unreachable!(),
|
||||
/// Parse legal hold status string into ObjectLockLegalHoldStatus.
|
||||
/// Returns None for invalid/unknown status strings instead of panicking.
|
||||
pub fn parse_legalhold_status(hold_str: &str) -> Option<ObjectLockLegalHoldStatus> {
|
||||
match hold_str.to_uppercase().as_str() {
|
||||
"ON" => Some(ObjectLockLegalHoldStatus::from_static(ObjectLockLegalHoldStatus::ON)),
|
||||
"OFF" => Some(ObjectLockLegalHoldStatus::from_static(ObjectLockLegalHoldStatus::OFF)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_ret_mode_valid() {
|
||||
// Test uppercase
|
||||
let mode = parse_ret_mode("GOVERNANCE");
|
||||
assert!(mode.is_some());
|
||||
assert_eq!(mode.unwrap().as_str(), ObjectLockRetentionMode::GOVERNANCE);
|
||||
|
||||
let mode = parse_ret_mode("COMPLIANCE");
|
||||
assert!(mode.is_some());
|
||||
assert_eq!(mode.unwrap().as_str(), ObjectLockRetentionMode::COMPLIANCE);
|
||||
|
||||
// Test lowercase
|
||||
let mode = parse_ret_mode("governance");
|
||||
assert!(mode.is_some());
|
||||
assert_eq!(mode.unwrap().as_str(), ObjectLockRetentionMode::GOVERNANCE);
|
||||
|
||||
let mode = parse_ret_mode("compliance");
|
||||
assert!(mode.is_some());
|
||||
assert_eq!(mode.unwrap().as_str(), ObjectLockRetentionMode::COMPLIANCE);
|
||||
|
||||
// Test mixed case
|
||||
let mode = parse_ret_mode("Governance");
|
||||
assert!(mode.is_some());
|
||||
assert_eq!(mode.unwrap().as_str(), ObjectLockRetentionMode::GOVERNANCE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_ret_mode_invalid() {
|
||||
// Test invalid values return None instead of panicking
|
||||
assert!(parse_ret_mode("INVALID").is_none());
|
||||
assert!(parse_ret_mode("").is_none());
|
||||
assert!(parse_ret_mode("gov").is_none());
|
||||
assert!(parse_ret_mode("comp").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_legalhold_status_valid() {
|
||||
// Test uppercase
|
||||
let status = parse_legalhold_status("ON");
|
||||
assert!(status.is_some());
|
||||
assert_eq!(status.unwrap().as_str(), ObjectLockLegalHoldStatus::ON);
|
||||
|
||||
let status = parse_legalhold_status("OFF");
|
||||
assert!(status.is_some());
|
||||
assert_eq!(status.unwrap().as_str(), ObjectLockLegalHoldStatus::OFF);
|
||||
|
||||
// Test lowercase
|
||||
let status = parse_legalhold_status("on");
|
||||
assert!(status.is_some());
|
||||
assert_eq!(status.unwrap().as_str(), ObjectLockLegalHoldStatus::ON);
|
||||
|
||||
let status = parse_legalhold_status("off");
|
||||
assert!(status.is_some());
|
||||
assert_eq!(status.unwrap().as_str(), ObjectLockLegalHoldStatus::OFF);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_legalhold_status_invalid() {
|
||||
// Test invalid values return None instead of panicking
|
||||
assert!(parse_legalhold_status("INVALID").is_none());
|
||||
assert!(parse_legalhold_status("").is_none());
|
||||
assert!(parse_legalhold_status("true").is_none());
|
||||
assert!(parse_legalhold_status("false").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_object_retention_meta_empty() {
|
||||
let meta = HashMap::new();
|
||||
let retention = get_object_retention_meta(&meta);
|
||||
assert!(retention.mode.is_none());
|
||||
assert!(retention.retain_until_date.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_object_retention_meta_with_mode() {
|
||||
let mut meta = HashMap::new();
|
||||
meta.insert("x-amz-object-lock-mode".to_string(), "GOVERNANCE".to_string());
|
||||
let retention = get_object_retention_meta(&meta);
|
||||
assert!(retention.mode.is_some());
|
||||
assert_eq!(retention.mode.unwrap().as_str(), ObjectLockRetentionMode::GOVERNANCE);
|
||||
assert!(retention.retain_until_date.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_object_retention_meta_with_invalid_mode() {
|
||||
let mut meta = HashMap::new();
|
||||
meta.insert("x-amz-object-lock-mode".to_string(), "INVALID_MODE".to_string());
|
||||
let retention = get_object_retention_meta(&meta);
|
||||
// Invalid mode should return empty retention, not panic
|
||||
assert!(retention.mode.is_none());
|
||||
assert!(retention.retain_until_date.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_object_retention_meta_with_date() {
|
||||
let mut meta = HashMap::new();
|
||||
meta.insert("x-amz-object-lock-mode".to_string(), "COMPLIANCE".to_string());
|
||||
meta.insert("x-amz-object-lock-retain-until-date".to_string(), "2030-01-01T00:00:00Z".to_string());
|
||||
let retention = get_object_retention_meta(&meta);
|
||||
assert!(retention.mode.is_some());
|
||||
assert_eq!(retention.mode.unwrap().as_str(), ObjectLockRetentionMode::COMPLIANCE);
|
||||
assert!(retention.retain_until_date.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_object_legalhold_meta_empty() {
|
||||
let meta = HashMap::new();
|
||||
let legalhold = get_object_legalhold_meta(&meta);
|
||||
assert!(legalhold.status.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_object_legalhold_meta_on() {
|
||||
let mut meta = HashMap::new();
|
||||
meta.insert("x-amz-object-lock-legal-hold".to_string(), "ON".to_string());
|
||||
let legalhold = get_object_legalhold_meta(&meta);
|
||||
assert!(legalhold.status.is_some());
|
||||
assert_eq!(legalhold.status.unwrap().as_str(), ObjectLockLegalHoldStatus::ON);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_object_legalhold_meta_off() {
|
||||
let mut meta = HashMap::new();
|
||||
meta.insert("x-amz-object-lock-legal-hold".to_string(), "OFF".to_string());
|
||||
let legalhold = get_object_legalhold_meta(&meta);
|
||||
assert!(legalhold.status.is_some());
|
||||
assert_eq!(legalhold.status.unwrap().as_str(), ObjectLockLegalHoldStatus::OFF);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_object_legalhold_meta_invalid() {
|
||||
let mut meta = HashMap::new();
|
||||
meta.insert("x-amz-object-lock-legal-hold".to_string(), "INVALID".to_string());
|
||||
let legalhold = get_object_legalhold_meta(&meta);
|
||||
// Invalid status should return None, not panic
|
||||
assert!(legalhold.status.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user