1#[derive(Debug, Clone, PartialEq, Eq)]
6pub enum LicenseState {
7 Trial { days_left: i64 },
8 Licensed,
9 Limited,
10 FreeTier,
11 Expired,
12 Invalid,
13}
14
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum TrialStatus {
17 NotStarted,
18 Active { days_left: i64 },
19 Expired,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum KeylessState {
24 Trial,
25 FreeTier,
26 Expired,
27}
28impl KeylessState {
29 pub fn wire(&self) -> &'static str {
30 match self {
31 Self::Trial => "trial",
32 Self::FreeTier => "free_tier",
33 Self::Expired => "expired",
34 }
35 }
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum LicenseLifecycleEvent {
40 Renewed,
41 Cancelled,
42 Expired,
43 Restored,
44}
45
46pub fn resolve_state(
50 lease_status: Option<&str>,
51 lease_current: bool,
52 had_license: bool,
53 trial: &TrialStatus,
54 free_tier_enabled: bool,
55) -> LicenseState {
56 if let Some(status) = lease_status {
57 match (status, lease_current) {
58 ("active", true) => return LicenseState::Licensed,
59 ("fallback", _) => return LicenseState::Limited,
60 ("expired", _) => return LicenseState::Expired,
61 _ => {}
63 }
64 }
65 if had_license {
66 return LicenseState::Expired;
67 }
68 match trial {
69 TrialStatus::Active { days_left } => LicenseState::Trial {
70 days_left: *days_left,
71 },
72 _ if free_tier_enabled => LicenseState::FreeTier,
73 _ => LicenseState::Invalid,
74 }
75}
76
77pub fn lifecycle_event(
78 prev: &LicenseState,
79 next: &LicenseState,
80 expiry_moved_later: bool,
81) -> Option<LicenseLifecycleEvent> {
82 use LicenseState::*;
83 match (prev, next) {
84 (Licensed, Licensed) if expiry_moved_later => Some(LicenseLifecycleEvent::Renewed),
85 (Licensed, Expired) | (Licensed, Limited) => Some(LicenseLifecycleEvent::Cancelled),
86 (Expired, Licensed) | (Limited, Licensed) | (Invalid, Licensed) => {
87 Some(LicenseLifecycleEvent::Restored)
88 }
89 (_, Expired) if !matches!(prev, Expired) => Some(LicenseLifecycleEvent::Expired),
90 _ => None,
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97 #[test]
98 fn active_current_lease_is_licensed() {
99 assert_eq!(
100 resolve_state(Some("active"), true, true, &TrialStatus::NotStarted, false),
101 LicenseState::Licensed
102 );
103 }
104 #[test]
105 fn fallback_is_limited() {
106 assert_eq!(
107 resolve_state(
108 Some("fallback"),
109 true,
110 true,
111 &TrialStatus::NotStarted,
112 false
113 ),
114 LicenseState::Limited
115 );
116 }
117 #[test]
118 fn no_license_trial_active_is_trial() {
119 assert_eq!(
120 resolve_state(
121 None,
122 false,
123 false,
124 &TrialStatus::Active { days_left: 5 },
125 false
126 ),
127 LicenseState::Trial { days_left: 5 }
128 );
129 }
130 #[test]
131 fn no_license_free_tier_is_free_tier() {
132 assert_eq!(
133 resolve_state(None, false, false, &TrialStatus::NotStarted, true),
134 LicenseState::FreeTier
135 );
136 }
137 #[test]
138 fn nothing_is_invalid() {
139 assert_eq!(
140 resolve_state(None, false, false, &TrialStatus::NotStarted, false),
141 LicenseState::Invalid
142 );
143 }
144 #[test]
145 fn keyless_wire_strings() {
146 assert_eq!(KeylessState::FreeTier.wire(), "free_tier");
147 }
148
149 use LicenseLifecycleEvent as E;
150 use LicenseState as S;
151 #[test]
152 fn renewed_when_licensed_and_expiry_later() {
153 assert_eq!(
154 lifecycle_event(&S::Licensed, &S::Licensed, true),
155 Some(E::Renewed)
156 );
157 assert_eq!(lifecycle_event(&S::Licensed, &S::Licensed, false), None);
158 }
159 #[test]
160 fn cancelled_on_licensed_to_expired_or_limited() {
161 assert_eq!(
162 lifecycle_event(&S::Licensed, &S::Expired, false),
163 Some(E::Cancelled)
164 );
165 assert_eq!(
166 lifecycle_event(&S::Licensed, &S::Limited, false),
167 Some(E::Cancelled)
168 );
169 }
170 #[test]
171 fn restored_on_recovery_to_licensed() {
172 assert_eq!(
173 lifecycle_event(&S::Expired, &S::Licensed, false),
174 Some(E::Restored)
175 );
176 assert_eq!(
177 lifecycle_event(&S::Limited, &S::Licensed, false),
178 Some(E::Restored)
179 );
180 assert_eq!(
181 lifecycle_event(&S::Invalid, &S::Licensed, false),
182 Some(E::Restored)
183 );
184 }
185 #[test]
186 fn expired_when_crossing_into_expired_from_non_expired() {
187 assert_eq!(
188 lifecycle_event(&S::Trial { days_left: 1 }, &S::Expired, false),
189 Some(E::Expired)
190 );
191 }
192 #[test]
193 fn no_event_on_noop_transitions() {
194 assert_eq!(
195 lifecycle_event(
196 &S::Trial { days_left: 3 },
197 &S::Trial { days_left: 2 },
198 false
199 ),
200 None
201 );
202 assert_eq!(lifecycle_event(&S::Expired, &S::Expired, false), None);
203 }
204}