Skip to main content

reauth_types/
subscription.rs

1use serde::{Deserialize, Serialize};
2
3/// Subscription status values used in JWT claims and API responses.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
5#[serde(rename_all = "snake_case")]
6pub enum SubscriptionStatus {
7    Active,
8    PastDue,
9    Canceled,
10    Trialing,
11    Incomplete,
12    IncompleteExpired,
13    Unpaid,
14    Paused,
15    #[default]
16    None,
17}
18
19impl SubscriptionStatus {
20    /// Returns true if the subscription is in an active state (active or trialing).
21    pub fn is_active(&self) -> bool {
22        matches!(self, Self::Active | Self::Trialing)
23    }
24
25    /// Returns true if the subscription is in a grace period (past due but not yet canceled).
26    pub fn is_grace_period(&self) -> bool {
27        matches!(self, Self::PastDue)
28    }
29
30    /// Returns true if the subscription allows access to paid features.
31    pub fn has_access(&self) -> bool {
32        matches!(self, Self::Active | Self::Trialing | Self::PastDue)
33    }
34}
35
36impl std::fmt::Display for SubscriptionStatus {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        let s = match self {
39            Self::Active => "active",
40            Self::PastDue => "past_due",
41            Self::Canceled => "canceled",
42            Self::Trialing => "trialing",
43            Self::Incomplete => "incomplete",
44            Self::IncompleteExpired => "incomplete_expired",
45            Self::Unpaid => "unpaid",
46            Self::Paused => "paused",
47            Self::None => "none",
48        };
49        write!(f, "{}", s)
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn test_serde_roundtrip() {
59        let status = SubscriptionStatus::Active;
60        let json = serde_json::to_string(&status).unwrap();
61        assert_eq!(json, r#""active""#);
62
63        let parsed: SubscriptionStatus = serde_json::from_str(&json).unwrap();
64        assert_eq!(parsed, status);
65    }
66
67    #[test]
68    fn test_snake_case_serialization() {
69        let status = SubscriptionStatus::PastDue;
70        let json = serde_json::to_string(&status).unwrap();
71        assert_eq!(json, r#""past_due""#);
72    }
73
74    #[test]
75    fn test_is_active() {
76        assert!(SubscriptionStatus::Active.is_active());
77        assert!(SubscriptionStatus::Trialing.is_active());
78        assert!(!SubscriptionStatus::Canceled.is_active());
79        assert!(!SubscriptionStatus::None.is_active());
80    }
81
82    #[test]
83    fn test_has_access() {
84        assert!(SubscriptionStatus::Active.has_access());
85        assert!(SubscriptionStatus::Trialing.has_access());
86        assert!(SubscriptionStatus::PastDue.has_access());
87        assert!(!SubscriptionStatus::Canceled.has_access());
88        assert!(!SubscriptionStatus::None.has_access());
89    }
90}