1use semver::Version;
6use strum::{AsRefStr, EnumString};
7use time::OffsetDateTime;
8
9#[cfg_attr(
11 feature = "serde",
12 derive(serde::Serialize, serde::Deserialize),
13 serde(rename_all = "camelCase")
14)]
15#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
16#[cfg_attr(not(feature = "unstable"), non_exhaustive)]
17pub struct LatestVersion {
18 pub repository: String,
20 pub latest_tag: Version,
22 #[cfg_attr(feature = "serde", serde(with = "time::serde::iso8601"))]
23 pub pushed_at: OffsetDateTime,
24 pub digest: String,
25}
26
27#[cfg_attr(
29 feature = "serde",
30 derive(serde::Serialize, serde::Deserialize),
31 serde(rename_all = "camelCase")
32)]
33#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
34#[cfg_attr(not(feature = "unstable"), non_exhaustive)]
35pub struct RegenerateResponse {
36 pub account_id: u64,
37 pub license_id: u32,
38 #[cfg_attr(feature = "serde", serde(with = "time::serde::iso8601"))]
39 pub expires_at: OffsetDateTime,
40 pub license_key: String,
41}
42
43#[cfg_attr(
45 feature = "serde",
46 derive(serde::Serialize, serde::Deserialize),
47 serde(rename_all = "camelCase")
48)]
49#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
50#[cfg_attr(not(feature = "unstable"), non_exhaustive)]
51pub struct VerifyResponse {
52 pub valid: bool,
53 pub license_id: Option<u32>,
54 #[cfg_attr(
55 feature = "serde",
56 serde(default, with = "time::serde::iso8601::option")
57 )]
58 pub expires_at: Option<OffsetDateTime>,
59 pub reason: Option<String>,
60}
61
62#[cfg_attr(
67 feature = "serde",
68 derive(serde::Serialize, serde::Deserialize),
69 serde(rename_all = "camelCase")
70)]
71#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
72#[cfg_attr(not(feature = "unstable"), non_exhaustive)]
73pub struct View(pub Vec<ViewOne>);
74
75#[cfg_attr(
79 feature = "serde",
80 derive(serde::Serialize, serde::Deserialize),
81 serde(rename_all = "camelCase")
82)]
83#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
84#[cfg_attr(not(feature = "unstable"), non_exhaustive)]
85pub struct ViewOne {
86 pub id: u32,
87 pub account_id: u64,
88 pub status: Status,
89 #[cfg_attr(feature = "serde", serde(with = "time::serde::iso8601"))]
90 pub expires_at: OffsetDateTime,
91 #[cfg_attr(
92 feature = "serde",
93 serde(default, with = "time::serde::iso8601::option")
94 )]
95 pub last_used_at: Option<OffsetDateTime>,
96 #[cfg_attr(feature = "serde", serde(with = "time::serde::iso8601"))]
97 pub created: OffsetDateTime,
98 #[cfg_attr(feature = "serde", serde(with = "time::serde::iso8601"))]
99 pub modified: OffsetDateTime,
100 pub key_version: u32,
101}
102
103#[cfg_attr(
108 feature = "serde",
109 derive(serde::Serialize, serde::Deserialize),
110 serde(rename_all = "SCREAMING_SNAKE_CASE")
111)]
112#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, AsRefStr, EnumString)]
113#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
114#[cfg_attr(not(feature = "unstable"), non_exhaustive)]
115pub enum Status {
116 Active,
118 Inactive,
120}
121
122#[cfg(all(test, feature = "serde"))]
123mod tests {
124 use time::macros::datetime;
125
126 use super::*;
127
128 #[test]
129 fn view_all() {
130 let json = r#"[
131 {
132 "id": 1,
133 "accountId": 1,
134 "status": "ACTIVE",
135 "expiresAt": "2025-12-11T00:00:00Z",
136 "lastUsedAt": "2025-12-09T23:15:10.520830Z",
137 "created": "2025-12-09T23:14:22.482359Z",
138 "modified": "2025-12-09T23:15:10.522040Z",
139 "keyVersion": 1
140 },
141 {
142 "id": 2,
143 "accountId": 1,
144 "status": "ACTIVE",
145 "expiresAt": "2025-12-11T00:00:00Z",
146 "created": "2025-12-10T17:31:59.709112Z",
147 "modified": "2025-12-10T17:31:59.709112Z",
148 "keyVersion": 1
149 }
150]"#;
151 let view: View = serde_json::from_str(json).unwrap();
152 let should_be = View(vec![
153 ViewOne {
154 id: 1,
155 account_id: 1,
156 status: Status::Active,
157 expires_at: datetime!(2025 - 12 - 11 00:00:00).assume_utc(),
158 last_used_at: Some(datetime!(2025 - 12 - 09 23:15:10.520_830).assume_utc()),
159 created: datetime!(2025 - 12 - 09 23:14:22.482_359).assume_utc(),
160 modified: datetime!(2025 - 12 - 09 23:15:10.522_040).assume_utc(),
161 key_version: 1,
162 },
163 ViewOne {
164 id: 2,
165 account_id: 1,
166 status: Status::Active,
167 expires_at: datetime!(2025 - 12 - 11 00:00:00).assume_utc(),
168 last_used_at: None,
169 created: datetime!(2025 - 12 - 10 17:31:59.709_112).assume_utc(),
170 modified: datetime!(2025 - 12 - 10 17:31:59.709_112).assume_utc(),
171 key_version: 1,
172 },
173 ]);
174 assert_eq!(view, should_be);
175 }
176
177 #[test]
178 fn view_one() {
179 let json = r#"
180 {
181 "id": 1,
182 "accountId": 1,
183 "status": "ACTIVE",
184 "expiresAt": "2025-12-11T00:00:00Z",
185 "lastUsedAt": "2025-12-09T23:15:10.520830Z",
186 "created": "2025-12-09T23:14:22.482359Z",
187 "modified": "2025-12-09T23:15:10.522040Z",
188 "keyVersion": 1
189 }"#;
190 let view: ViewOne = serde_json::from_str(json).unwrap();
191 let should_be = ViewOne {
192 id: 1,
193 account_id: 1,
194 status: Status::Active,
195 expires_at: datetime!(2025 - 12 - 11 00:00:00).assume_utc(),
196 last_used_at: Some(datetime!(2025 - 12 - 09 23:15:10.520_830).assume_utc()),
197 created: datetime!(2025 - 12 - 09 23:14:22.482_359).assume_utc(),
198 modified: datetime!(2025 - 12 - 09 23:15:10.522_040).assume_utc(),
199 key_version: 1,
200 };
201 assert_eq!(view, should_be);
202 }
203
204 #[test]
205 fn view_one_missing_last_used() {
206 let json = r#"
207 {
208 "id": 1,
209 "accountId": 1,
210 "status": "ACTIVE",
211 "expiresAt": "2025-12-11T00:00:00Z",
212 "created": "2025-12-09T23:14:22.482359Z",
213 "modified": "2025-12-09T23:15:10.522040Z",
214 "keyVersion": 1
215 }"#;
216 let view: ViewOne = serde_json::from_str(json).unwrap();
217 let should_be = ViewOne {
218 id: 1,
219 account_id: 1,
220 status: Status::Active,
221 expires_at: datetime!(2025 - 12 - 11 00:00:00).assume_utc(),
222 last_used_at: None,
223 created: datetime!(2025 - 12 - 09 23:14:22.482_359).assume_utc(),
224 modified: datetime!(2025 - 12 - 09 23:15:10.522_040).assume_utc(),
225 key_version: 1,
226 };
227 assert_eq!(view, should_be);
228 }
229
230 #[test]
231 fn regenerate_response() {
232 let json = r#"{
233 "licenseId": 1,
234 "accountId": 1,
235 "expiresAt": "2025-12-11T00:00:00Z",
236 "licenseKey": "foobar"
237}"#;
238 let regenerate: RegenerateResponse = serde_json::from_str(json).unwrap();
239 let should_be = RegenerateResponse {
240 account_id: 1,
241 license_id: 1,
242 expires_at: datetime!(2025 - 12 - 11 00:00:00).assume_utc(),
243 license_key: String::from("foobar"),
244 };
245 assert_eq!(regenerate, should_be);
246 }
247
248 #[test]
249 fn verify_response_valid() {
250 let json = r#"{
251 "valid": true,
252 "licenseId": 1,
253 "expiresAt": "2025-12-11T00:00:00Z"
254}"#;
255 let verify: VerifyResponse = serde_json::from_str(json).unwrap();
256 let should_be = VerifyResponse {
257 valid: true,
258 license_id: Some(1),
259 expires_at: Some(datetime!(2025 - 12 - 11 00:00:00).assume_utc()),
260 reason: None,
261 };
262 assert_eq!(verify, should_be);
263 }
264
265 #[test]
266 fn verify_response_invalid() {
267 let json = r#"{
268 "valid": false,
269 "reason": "INVALID"
270}"#;
271 let verify: VerifyResponse = serde_json::from_str(json).unwrap();
272 let should_be = VerifyResponse {
273 valid: false,
274 license_id: None,
275 expires_at: None,
276 reason: Some(String::from("INVALID")),
277 };
278 assert_eq!(verify, should_be);
279 }
280}