use std::fmt;
use jiff::Timestamp;
use serde::{Deserialize, Serialize};
use crate::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum RetentionMode {
Governance,
Compliance,
}
impl fmt::Display for RetentionMode {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::Governance => "governance",
Self::Compliance => "compliance",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum RetentionDurationUnit {
Days,
Years,
}
impl fmt::Display for RetentionDurationUnit {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::Days => "days",
Self::Years => "years",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct RetentionDuration {
pub unit: RetentionDurationUnit,
pub value: i32,
}
impl RetentionDuration {
pub fn days(value: i32) -> Result<Self> {
Self::new(RetentionDurationUnit::Days, value)
}
pub fn years(value: i32) -> Result<Self> {
Self::new(RetentionDurationUnit::Years, value)
}
fn new(unit: RetentionDurationUnit, value: i32) -> Result<Self> {
if value <= 0 {
return Err(Error::InvalidPath(
"Retention duration must be a positive number of days or years".to_string(),
));
}
Ok(Self { unit, value })
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DefaultRetention {
pub mode: RetentionMode,
pub duration: RetentionDuration,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BucketObjectLockConfiguration {
pub enabled: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub default_retention: Option<DefaultRetention>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ObjectRetention {
pub mode: RetentionMode,
pub retain_until: Timestamp,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum LegalHoldStatus {
Off,
On,
}
impl LegalHoldStatus {
pub const fn is_on(self) -> bool {
matches!(self, Self::On)
}
}
impl fmt::Display for LegalHoldStatus {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::Off => "off",
Self::On => "on",
})
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ObjectLockOptions {
pub version_id: Option<String>,
pub bypass_governance: bool,
}
impl ObjectLockOptions {
pub fn new(version_id: Option<String>, bypass_governance: bool) -> Result<Self> {
if version_id.as_deref().is_some_and(str::is_empty) {
return Err(Error::InvalidPath("Version ID cannot be empty".to_string()));
}
Ok(Self {
version_id,
bypass_governance,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn retention_duration_is_positive_and_unambiguous() {
assert_eq!(
RetentionDuration::days(30).expect("positive days"),
RetentionDuration {
unit: RetentionDurationUnit::Days,
value: 30,
}
);
assert_eq!(
RetentionDuration::years(2).expect("positive years"),
RetentionDuration {
unit: RetentionDurationUnit::Years,
value: 2,
}
);
assert!(RetentionDuration::days(0).is_err());
assert!(RetentionDuration::years(-1).is_err());
}
#[test]
fn object_lock_options_reject_empty_versions() {
assert!(ObjectLockOptions::new(Some(String::new()), false).is_err());
assert_eq!(
ObjectLockOptions::new(Some("v1".to_string()), true)
.expect("non-empty version")
.version_id
.as_deref(),
Some("v1")
);
}
}