1use std::fmt;
4
5use jiff::Timestamp;
6use serde::{Deserialize, Serialize};
7
8use crate::{Error, Result};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(rename_all = "lowercase")]
13pub enum RetentionMode {
14 Governance,
16 Compliance,
18}
19
20impl fmt::Display for RetentionMode {
21 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
22 formatter.write_str(match self {
23 Self::Governance => "governance",
24 Self::Compliance => "compliance",
25 })
26 }
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
31#[serde(rename_all = "lowercase")]
32pub enum RetentionDurationUnit {
33 Days,
35 Years,
37}
38
39impl fmt::Display for RetentionDurationUnit {
40 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
41 formatter.write_str(match self {
42 Self::Days => "days",
43 Self::Years => "years",
44 })
45 }
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
50pub struct RetentionDuration {
51 pub unit: RetentionDurationUnit,
53 pub value: i32,
55}
56
57impl RetentionDuration {
58 pub fn days(value: i32) -> Result<Self> {
60 Self::new(RetentionDurationUnit::Days, value)
61 }
62
63 pub fn years(value: i32) -> Result<Self> {
65 Self::new(RetentionDurationUnit::Years, value)
66 }
67
68 fn new(unit: RetentionDurationUnit, value: i32) -> Result<Self> {
69 if value <= 0 {
70 return Err(Error::InvalidPath(
71 "Retention duration must be a positive number of days or years".to_string(),
72 ));
73 }
74 Ok(Self { unit, value })
75 }
76}
77
78#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
80pub struct DefaultRetention {
81 pub mode: RetentionMode,
83 pub duration: RetentionDuration,
85}
86
87#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
89pub struct BucketObjectLockConfiguration {
90 pub enabled: bool,
92 #[serde(skip_serializing_if = "Option::is_none")]
94 pub default_retention: Option<DefaultRetention>,
95}
96
97#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
99pub struct ObjectRetention {
100 pub mode: RetentionMode,
102 pub retain_until: Timestamp,
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
108#[serde(rename_all = "lowercase")]
109pub enum LegalHoldStatus {
110 Off,
112 On,
114}
115
116impl LegalHoldStatus {
117 pub const fn is_on(self) -> bool {
119 matches!(self, Self::On)
120 }
121}
122
123impl fmt::Display for LegalHoldStatus {
124 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
125 formatter.write_str(match self {
126 Self::Off => "off",
127 Self::On => "on",
128 })
129 }
130}
131
132#[derive(Debug, Clone, Default, PartialEq, Eq)]
134pub struct ObjectLockOptions {
135 pub version_id: Option<String>,
137 pub bypass_governance: bool,
139}
140
141impl ObjectLockOptions {
142 pub fn new(version_id: Option<String>, bypass_governance: bool) -> Result<Self> {
144 if version_id.as_deref().is_some_and(str::is_empty) {
145 return Err(Error::InvalidPath("Version ID cannot be empty".to_string()));
146 }
147 Ok(Self {
148 version_id,
149 bypass_governance,
150 })
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn retention_duration_is_positive_and_unambiguous() {
160 assert_eq!(
161 RetentionDuration::days(30).expect("positive days"),
162 RetentionDuration {
163 unit: RetentionDurationUnit::Days,
164 value: 30,
165 }
166 );
167 assert_eq!(
168 RetentionDuration::years(2).expect("positive years"),
169 RetentionDuration {
170 unit: RetentionDurationUnit::Years,
171 value: 2,
172 }
173 );
174 assert!(RetentionDuration::days(0).is_err());
175 assert!(RetentionDuration::years(-1).is_err());
176 }
177
178 #[test]
179 fn object_lock_options_reject_empty_versions() {
180 assert!(ObjectLockOptions::new(Some(String::new()), false).is_err());
181 assert_eq!(
182 ObjectLockOptions::new(Some("v1".to_string()), true)
183 .expect("non-empty version")
184 .version_id
185 .as_deref(),
186 Some("v1")
187 );
188 }
189}