durable_execution_sdk/duration.rs
1//! Duration type for durable execution operations.
2//!
3//! Provides a Duration type with convenient constructors for specifying
4//! time intervals in seconds, minutes, hours, days, weeks, months, and years.
5
6use serde::{Deserialize, Serialize};
7
8use crate::error::DurableError;
9
10/// Duration type representing a time interval in seconds.
11///
12/// Used for configuring timeouts, wait operations, and other time-based settings.
13///
14/// # Example
15///
16/// ```
17/// use durable_execution_sdk::Duration;
18///
19/// let five_seconds = Duration::from_seconds(5);
20/// let two_minutes = Duration::from_minutes(2);
21/// let one_hour = Duration::from_hours(1);
22/// let one_day = Duration::from_days(1);
23/// let one_week = Duration::from_weeks(1);
24/// let one_month = Duration::from_months(1);
25/// let one_year = Duration::from_years(1);
26///
27/// assert_eq!(five_seconds.to_seconds(), 5);
28/// assert_eq!(two_minutes.to_seconds(), 120);
29/// assert_eq!(one_hour.to_seconds(), 3600);
30/// assert_eq!(one_day.to_seconds(), 86400);
31/// assert_eq!(one_week.to_seconds(), 604800);
32/// assert_eq!(one_month.to_seconds(), 2592000); // 30 days
33/// assert_eq!(one_year.to_seconds(), 31536000); // 365 days
34/// ```
35#[derive(
36 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default,
37)]
38pub struct Duration {
39 seconds: u64,
40}
41
42impl Duration {
43 /// Creates a new Duration from the given number of seconds.
44 ///
45 /// # Arguments
46 ///
47 /// * `seconds` - The number of seconds for this duration
48 ///
49 /// # Example
50 ///
51 /// ```
52 /// use durable_execution_sdk::Duration;
53 ///
54 /// let duration = Duration::from_seconds(30);
55 /// assert_eq!(duration.to_seconds(), 30);
56 /// ```
57 pub fn from_seconds(seconds: u64) -> Self {
58 Self { seconds }
59 }
60
61 /// Creates a new Duration from the given number of minutes.
62 ///
63 /// # Arguments
64 ///
65 /// * `minutes` - The number of minutes for this duration
66 ///
67 /// # Example
68 ///
69 /// ```
70 /// use durable_execution_sdk::Duration;
71 ///
72 /// let duration = Duration::from_minutes(5);
73 /// assert_eq!(duration.to_seconds(), 300);
74 /// ```
75 pub fn from_minutes(minutes: u64) -> Self {
76 Self {
77 seconds: minutes * 60,
78 }
79 }
80
81 /// Creates a new Duration from the given number of hours.
82 ///
83 /// # Arguments
84 ///
85 /// * `hours` - The number of hours for this duration
86 ///
87 /// # Example
88 ///
89 /// ```
90 /// use durable_execution_sdk::Duration;
91 ///
92 /// let duration = Duration::from_hours(2);
93 /// assert_eq!(duration.to_seconds(), 7200);
94 /// ```
95 pub fn from_hours(hours: u64) -> Self {
96 Self {
97 seconds: hours * 3600,
98 }
99 }
100
101 /// Creates a new Duration from the given number of days.
102 ///
103 /// # Arguments
104 ///
105 /// * `days` - The number of days for this duration
106 ///
107 /// # Example
108 ///
109 /// ```
110 /// use durable_execution_sdk::Duration;
111 ///
112 /// let duration = Duration::from_days(1);
113 /// assert_eq!(duration.to_seconds(), 86400);
114 /// ```
115 pub fn from_days(days: u64) -> Self {
116 Self {
117 seconds: days * 86400,
118 }
119 }
120
121 /// Creates a new Duration from the given number of weeks.
122 ///
123 /// # Arguments
124 ///
125 /// * `weeks` - The number of weeks for this duration
126 ///
127 /// # Example
128 ///
129 /// ```
130 /// use durable_execution_sdk::Duration;
131 ///
132 /// let duration = Duration::from_weeks(1);
133 /// assert_eq!(duration.to_seconds(), 604800);
134 /// ```
135 pub fn from_weeks(weeks: u64) -> Self {
136 Self {
137 seconds: weeks * 604800, // 7 days * 86400 seconds/day
138 }
139 }
140
141 /// Creates a new Duration from the given number of months.
142 ///
143 /// A month is defined as 30 days for consistency.
144 ///
145 /// # Arguments
146 ///
147 /// * `months` - The number of months for this duration
148 ///
149 /// # Example
150 ///
151 /// ```
152 /// use durable_execution_sdk::Duration;
153 ///
154 /// let duration = Duration::from_months(1);
155 /// assert_eq!(duration.to_seconds(), 2592000); // 30 days
156 /// ```
157 pub fn from_months(months: u64) -> Self {
158 Self {
159 seconds: months * 2592000, // 30 days * 86400 seconds/day
160 }
161 }
162
163 /// Creates a new Duration from the given number of years.
164 ///
165 /// A year is defined as 365 days for consistency.
166 ///
167 /// # Arguments
168 ///
169 /// * `years` - The number of years for this duration
170 ///
171 /// # Example
172 ///
173 /// ```
174 /// use durable_execution_sdk::Duration;
175 ///
176 /// let duration = Duration::from_years(1);
177 /// assert_eq!(duration.to_seconds(), 31536000); // 365 days
178 /// ```
179 pub fn from_years(years: u64) -> Self {
180 Self {
181 seconds: years * 31536000, // 365 days * 86400 seconds/day
182 }
183 }
184
185 /// Returns the total number of seconds in this duration.
186 ///
187 /// # Example
188 ///
189 /// ```
190 /// use durable_execution_sdk::Duration;
191 ///
192 /// let duration = Duration::from_minutes(2);
193 /// assert_eq!(duration.to_seconds(), 120);
194 /// ```
195 pub fn to_seconds(&self) -> u64 {
196 self.seconds
197 }
198
199 /// Validates that this duration is at least the minimum required for wait operations.
200 ///
201 /// Wait operations require a minimum duration of 1 second.
202 ///
203 /// # Returns
204 ///
205 /// * `Ok(())` if the duration is valid (>= 1 second)
206 /// * `Err(DurableError::Validation)` if the duration is less than 1 second
207 ///
208 /// # Example
209 ///
210 /// ```
211 /// use durable_execution_sdk::Duration;
212 ///
213 /// let valid = Duration::from_seconds(1);
214 /// assert!(valid.validate_for_wait().is_ok());
215 ///
216 /// let invalid = Duration::from_seconds(0);
217 /// assert!(invalid.validate_for_wait().is_err());
218 /// ```
219 pub fn validate_for_wait(&self) -> Result<(), DurableError> {
220 if self.seconds < 1 {
221 return Err(DurableError::Validation {
222 message: "Wait duration must be at least 1 second".to_string(),
223 });
224 }
225 Ok(())
226 }
227}
228
229impl From<std::time::Duration> for Duration {
230 fn from(duration: std::time::Duration) -> Self {
231 Self {
232 seconds: duration.as_secs(),
233 }
234 }
235}
236
237impl From<Duration> for std::time::Duration {
238 fn from(duration: Duration) -> Self {
239 std::time::Duration::from_secs(duration.seconds)
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246 use proptest::prelude::*;
247
248 #[test]
249 fn test_from_seconds() {
250 let duration = Duration::from_seconds(42);
251 assert_eq!(duration.to_seconds(), 42);
252 }
253
254 #[test]
255 fn test_from_minutes() {
256 let duration = Duration::from_minutes(5);
257 assert_eq!(duration.to_seconds(), 300);
258 }
259
260 #[test]
261 fn test_from_hours() {
262 let duration = Duration::from_hours(2);
263 assert_eq!(duration.to_seconds(), 7200);
264 }
265
266 #[test]
267 fn test_from_days() {
268 let duration = Duration::from_days(1);
269 assert_eq!(duration.to_seconds(), 86400);
270 }
271
272 #[test]
273 fn test_from_weeks() {
274 let duration = Duration::from_weeks(1);
275 assert_eq!(duration.to_seconds(), 604800);
276 }
277
278 #[test]
279 fn test_from_weeks_multiple() {
280 let duration = Duration::from_weeks(2);
281 assert_eq!(duration.to_seconds(), 1209600);
282 }
283
284 #[test]
285 fn test_from_months() {
286 let duration = Duration::from_months(1);
287 assert_eq!(duration.to_seconds(), 2592000); // 30 days
288 }
289
290 #[test]
291 fn test_from_months_multiple() {
292 let duration = Duration::from_months(3);
293 assert_eq!(duration.to_seconds(), 7776000); // 90 days
294 }
295
296 #[test]
297 fn test_from_years() {
298 let duration = Duration::from_years(1);
299 assert_eq!(duration.to_seconds(), 31536000); // 365 days
300 }
301
302 #[test]
303 fn test_from_years_multiple() {
304 let duration = Duration::from_years(2);
305 assert_eq!(duration.to_seconds(), 63072000); // 730 days
306 }
307
308 #[test]
309 fn test_validate_for_wait_valid() {
310 let duration = Duration::from_seconds(1);
311 assert!(duration.validate_for_wait().is_ok());
312
313 let duration = Duration::from_seconds(100);
314 assert!(duration.validate_for_wait().is_ok());
315 }
316
317 #[test]
318 fn test_validate_for_wait_invalid() {
319 let duration = Duration::from_seconds(0);
320 assert!(duration.validate_for_wait().is_err());
321 }
322
323 #[test]
324 fn test_std_duration_conversion() {
325 let std_duration = std::time::Duration::from_secs(60);
326 let duration: Duration = std_duration.into();
327 assert_eq!(duration.to_seconds(), 60);
328
329 let back: std::time::Duration = duration.into();
330 assert_eq!(back.as_secs(), 60);
331 }
332
333 // Property-based tests
334 // **Feature: durable-execution-rust-sdk, Property 8: Duration Validation**
335 // **Validates: Requirements 5.4, 12.7**
336 proptest! {
337 /// Property: For any Duration value, constructing from seconds/minutes/hours/days
338 /// SHALL produce the correct total seconds.
339 #[test]
340 fn prop_duration_from_seconds_produces_correct_total(seconds in 0u64..=u64::MAX / 86400) {
341 let duration = Duration::from_seconds(seconds);
342 prop_assert_eq!(duration.to_seconds(), seconds);
343 }
344
345 #[test]
346 fn prop_duration_from_minutes_produces_correct_total(minutes in 0u64..=u64::MAX / 86400 / 60) {
347 let duration = Duration::from_minutes(minutes);
348 prop_assert_eq!(duration.to_seconds(), minutes * 60);
349 }
350
351 #[test]
352 fn prop_duration_from_hours_produces_correct_total(hours in 0u64..=u64::MAX / 86400 / 3600) {
353 let duration = Duration::from_hours(hours);
354 prop_assert_eq!(duration.to_seconds(), hours * 3600);
355 }
356
357 #[test]
358 fn prop_duration_from_days_produces_correct_total(days in 0u64..=u64::MAX / 86400 / 86400) {
359 let duration = Duration::from_days(days);
360 prop_assert_eq!(duration.to_seconds(), days * 86400);
361 }
362
363 #[test]
364 fn prop_duration_from_weeks_produces_correct_total(weeks in 0u64..=u64::MAX / 604800 / 604800) {
365 let duration = Duration::from_weeks(weeks);
366 prop_assert_eq!(duration.to_seconds(), weeks * 604800);
367 }
368
369 #[test]
370 fn prop_duration_from_months_produces_correct_total(months in 0u64..=u64::MAX / 2592000 / 2592000) {
371 let duration = Duration::from_months(months);
372 prop_assert_eq!(duration.to_seconds(), months * 2592000);
373 }
374
375 #[test]
376 fn prop_duration_from_years_produces_correct_total(years in 0u64..=u64::MAX / 31536000 / 31536000) {
377 let duration = Duration::from_years(years);
378 prop_assert_eq!(duration.to_seconds(), years * 31536000);
379 }
380
381 /// Property: Wait operations SHALL reject durations less than 1 second.
382 #[test]
383 fn prop_duration_validate_for_wait_rejects_zero(seconds in 0u64..1) {
384 let duration = Duration::from_seconds(seconds);
385 prop_assert!(duration.validate_for_wait().is_err());
386 }
387
388 /// Property: Wait operations SHALL accept durations >= 1 second.
389 #[test]
390 fn prop_duration_validate_for_wait_accepts_valid(seconds in 1u64..=1_000_000) {
391 let duration = Duration::from_seconds(seconds);
392 prop_assert!(duration.validate_for_wait().is_ok());
393 }
394
395 /// Property: Duration round-trip through std::time::Duration preserves value.
396 #[test]
397 fn prop_duration_std_roundtrip(seconds in 0u64..=u64::MAX / 2) {
398 let duration = Duration::from_seconds(seconds);
399 let std_duration: std::time::Duration = duration.into();
400 let back: Duration = std_duration.into();
401 prop_assert_eq!(duration, back);
402 }
403 }
404}