Skip to main content

tf_types/
expiration.rs

1//! Capability / authority / token expiration helpers — Rust mirror of
2//! `tools/tf-types-ts/src/core/expiration.ts`. Lexicographic RFC 3339
3//! comparison so byte-for-byte parity with TS holds when both sides use
4//! `Z`-suffixed UTC timestamps.
5
6#[derive(Clone, Debug, Default)]
7pub struct Window<'a> {
8    pub valid_from: Option<&'a str>,
9    pub valid_until: Option<&'a str>,
10    pub expires_at: Option<&'a str>,
11    pub not_before: Option<&'a str>,
12    pub not_after: Option<&'a str>,
13}
14
15#[derive(Clone, Debug, PartialEq, Eq)]
16pub enum ExpirationVerdict<'a> {
17    Ok,
18    NotYetValid { threshold: &'a str },
19    Expired { threshold: &'a str },
20}
21
22impl<'a> ExpirationVerdict<'a> {
23    pub fn ok(&self) -> bool {
24        matches!(self, ExpirationVerdict::Ok)
25    }
26}
27
28pub fn check_window<'a>(window: &'a Window<'a>, now: &str) -> ExpirationVerdict<'a> {
29    let start = window.valid_from.or(window.not_before);
30    let end = window
31        .valid_until
32        .or(window.expires_at)
33        .or(window.not_after);
34    if let Some(s) = start {
35        if now < s {
36            return ExpirationVerdict::NotYetValid { threshold: s };
37        }
38    }
39    if let Some(e) = end {
40        if now > e {
41            return ExpirationVerdict::Expired { threshold: e };
42        }
43    }
44    ExpirationVerdict::Ok
45}
46
47pub fn is_within_window(window: &Window<'_>, now: &str) -> bool {
48    matches!(check_window(window, now), ExpirationVerdict::Ok)
49}
50
51pub fn is_expired(window: &Window<'_>, now: &str) -> bool {
52    matches!(check_window(window, now), ExpirationVerdict::Expired { .. })
53}