Skip to main content

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}