torrust_tracker_deployer_lib/domain/backup/
retention_days.rs1use std::num::NonZeroU32;
4
5use serde::{Deserialize, Serialize};
6use thiserror::Error;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
13pub struct RetentionDays(NonZeroU32);
14
15#[derive(Debug, Error, PartialEq, Eq)]
17pub enum RetentionDaysError {
18 #[error("Retention days must be at least 1 (got 0)")]
20 Zero,
21}
22
23impl RetentionDays {
24 pub fn new(days: u32) -> Result<Self, RetentionDaysError> {
40 NonZeroU32::new(days)
41 .map(Self)
42 .ok_or(RetentionDaysError::Zero)
43 }
44
45 #[must_use]
47 pub const fn as_u32(self) -> u32 {
48 self.0.get()
49 }
50}
51
52impl Default for RetentionDays {
53 fn default() -> Self {
55 Self(NonZeroU32::new(7).expect("7 is non-zero"))
56 }
57}
58
59impl<'de> Deserialize<'de> for RetentionDays {
60 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
61 where
62 D: serde::Deserializer<'de>,
63 {
64 let days = u32::deserialize(deserializer)?;
65 Self::new(days).map_err(serde::de::Error::custom)
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use rstest::rstest;
72
73 use super::*;
74
75 #[rstest]
76 #[case(1)]
77 #[case(7)]
78 #[case(14)]
79 #[case(30)]
80 #[case(90)]
81 #[case(365)]
82 fn it_should_accept_valid_retention_days(#[case] days: u32) {
83 let result = RetentionDays::new(days);
84 assert!(result.is_ok(), "Retention days {days} should be valid");
85 assert_eq!(result.unwrap().as_u32(), days);
86 }
87
88 #[test]
89 fn it_should_reject_zero_days() {
90 let result = RetentionDays::new(0);
91 assert_eq!(result, Err(RetentionDaysError::Zero));
92 }
93
94 #[test]
95 fn it_should_use_sensible_default() {
96 let retention = RetentionDays::default();
97 assert_eq!(retention.as_u32(), 7);
98 }
99
100 #[test]
101 fn it_should_deserialize_valid_retention_days() {
102 let json = "7";
103 let retention: RetentionDays = serde_json::from_str(json).expect("valid retention");
104 assert_eq!(retention.as_u32(), 7);
105 }
106
107 #[test]
108 fn it_should_reject_zero_during_deserialization() {
109 let json = "0";
110 let result: Result<RetentionDays, _> = serde_json::from_str(json);
111 assert!(result.is_err(), "Zero should fail deserialization");
112 }
113
114 #[test]
115 fn it_should_serialize_and_deserialize_correctly() {
116 let original = RetentionDays::new(7).expect("valid retention");
117 let json = serde_json::to_string(&original).expect("serialization should succeed");
118 let deserialized: RetentionDays =
119 serde_json::from_str(&json).expect("deserialization should succeed");
120
121 assert_eq!(original, deserialized);
122 }
123}