Skip to content

Commit 02d81b5

Browse files
authored
feat: Add support for gateway license models (#2)
* feat: Add support for gateway license models * refactor!: Add reason field, and make expires_at optional * fix!: Switch to u64 for account * chore: Fix unused lints when testing without -F serde
1 parent 3beb6c4 commit 02d81b5

4 files changed

Lines changed: 283 additions & 2 deletions

File tree

src/gateway_licenses.rs

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
//! # Gateway Licenses
2+
//!
3+
//! Contains models for interacting with Freedom Gateway licensing endpoints.
4+
5+
use strum::{AsRefStr, EnumString};
6+
use time::OffsetDateTime;
7+
8+
/// Response body returned when regenerating a license key.
9+
#[cfg_attr(
10+
feature = "serde",
11+
derive(serde::Serialize, serde::Deserialize),
12+
serde(rename_all = "camelCase")
13+
)]
14+
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
15+
pub struct RegenerateResponse {
16+
pub account_id: u64,
17+
pub license_id: u32,
18+
#[cfg_attr(feature = "serde", serde(with = "time::serde::iso8601"))]
19+
pub expires_at: OffsetDateTime,
20+
pub license_key: String,
21+
}
22+
23+
/// Request body used to verify a license key.
24+
#[cfg_attr(
25+
feature = "serde",
26+
derive(serde::Serialize, serde::Deserialize),
27+
serde(rename_all = "camelCase")
28+
)]
29+
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
30+
pub struct Verify {
31+
pub license_key: String,
32+
}
33+
34+
/// Response body returned from a license verification request.
35+
#[cfg_attr(
36+
feature = "serde",
37+
derive(serde::Serialize, serde::Deserialize),
38+
serde(rename_all = "camelCase")
39+
)]
40+
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
41+
pub struct VerifyResponse {
42+
pub valid: bool,
43+
pub license_id: Option<u32>,
44+
#[cfg_attr(
45+
feature = "serde",
46+
serde(default, with = "time::serde::iso8601::option")
47+
)]
48+
pub expires_at: Option<OffsetDateTime>,
49+
pub reason: Option<String>,
50+
}
51+
52+
/// Response body for viewing all licenses associated with an account.
53+
///
54+
/// This is a wrapper type over a list of [`ViewOne`] items, corresponding to each license record
55+
/// for the account.
56+
#[cfg_attr(
57+
feature = "serde",
58+
derive(serde::Serialize, serde::Deserialize),
59+
serde(rename_all = "camelCase")
60+
)]
61+
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
62+
pub struct View(pub Vec<ViewOne>);
63+
64+
/// Representation of a single license associated with an account.
65+
///
66+
/// Used in license listing and detail-view responses.
67+
#[cfg_attr(
68+
feature = "serde",
69+
derive(serde::Serialize, serde::Deserialize),
70+
serde(rename_all = "camelCase")
71+
)]
72+
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
73+
pub struct ViewOne {
74+
pub id: u32,
75+
pub account_id: u64,
76+
pub status: Status,
77+
#[cfg_attr(feature = "serde", serde(with = "time::serde::iso8601"))]
78+
pub expires_at: OffsetDateTime,
79+
#[cfg_attr(
80+
feature = "serde",
81+
serde(default, with = "time::serde::iso8601::option")
82+
)]
83+
pub last_used_at: Option<OffsetDateTime>,
84+
#[cfg_attr(feature = "serde", serde(with = "time::serde::iso8601"))]
85+
pub created: OffsetDateTime,
86+
#[cfg_attr(feature = "serde", serde(with = "time::serde::iso8601"))]
87+
pub modified: OffsetDateTime,
88+
pub key_version: u32,
89+
}
90+
91+
/// The current status of a license.
92+
///
93+
/// Additional variants may be added in the future, so consumers should
94+
/// handle this enum non-exhaustively.
95+
#[cfg_attr(
96+
feature = "serde",
97+
derive(serde::Serialize, serde::Deserialize),
98+
serde(rename_all = "SCREAMING_SNAKE_CASE")
99+
)]
100+
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, AsRefStr, EnumString)]
101+
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
102+
#[non_exhaustive]
103+
pub enum Status {
104+
/// License is active and can be used.
105+
Active,
106+
/// License is inactive and should not be used.
107+
Inactive,
108+
}
109+
110+
#[cfg(all(test, feature = "serde"))]
111+
mod tests {
112+
use time::macros::datetime;
113+
114+
use super::*;
115+
116+
#[test]
117+
fn view_all() {
118+
let json = r#"[
119+
{
120+
"id": 1,
121+
"accountId": 1,
122+
"status": "ACTIVE",
123+
"expiresAt": "2025-12-11T00:00:00Z",
124+
"lastUsedAt": "2025-12-09T23:15:10.520830Z",
125+
"created": "2025-12-09T23:14:22.482359Z",
126+
"modified": "2025-12-09T23:15:10.522040Z",
127+
"keyVersion": 1
128+
},
129+
{
130+
"id": 2,
131+
"accountId": 1,
132+
"status": "ACTIVE",
133+
"expiresAt": "2025-12-11T00:00:00Z",
134+
"created": "2025-12-10T17:31:59.709112Z",
135+
"modified": "2025-12-10T17:31:59.709112Z",
136+
"keyVersion": 1
137+
}
138+
]"#;
139+
let view: View = serde_json::from_str(json).unwrap();
140+
let should_be = View(vec![
141+
ViewOne {
142+
id: 1,
143+
account_id: 1,
144+
status: Status::Active,
145+
expires_at: datetime!(2025 - 12 - 11 00:00:00).assume_utc(),
146+
last_used_at: Some(datetime!(2025 - 12 - 09 23:15:10.520_830).assume_utc()),
147+
created: datetime!(2025 - 12 - 09 23:14:22.482_359).assume_utc(),
148+
modified: datetime!(2025 - 12 - 09 23:15:10.522_040).assume_utc(),
149+
key_version: 1,
150+
},
151+
ViewOne {
152+
id: 2,
153+
account_id: 1,
154+
status: Status::Active,
155+
expires_at: datetime!(2025 - 12 - 11 00:00:00).assume_utc(),
156+
last_used_at: None,
157+
created: datetime!(2025 - 12 - 10 17:31:59.709_112).assume_utc(),
158+
modified: datetime!(2025 - 12 - 10 17:31:59.709_112).assume_utc(),
159+
key_version: 1,
160+
},
161+
]);
162+
assert_eq!(view, should_be);
163+
}
164+
165+
#[test]
166+
fn view_one() {
167+
let json = r#"
168+
{
169+
"id": 1,
170+
"accountId": 1,
171+
"status": "ACTIVE",
172+
"expiresAt": "2025-12-11T00:00:00Z",
173+
"lastUsedAt": "2025-12-09T23:15:10.520830Z",
174+
"created": "2025-12-09T23:14:22.482359Z",
175+
"modified": "2025-12-09T23:15:10.522040Z",
176+
"keyVersion": 1
177+
}"#;
178+
let view: ViewOne = serde_json::from_str(json).unwrap();
179+
let should_be = ViewOne {
180+
id: 1,
181+
account_id: 1,
182+
status: Status::Active,
183+
expires_at: datetime!(2025 - 12 - 11 00:00:00).assume_utc(),
184+
last_used_at: Some(datetime!(2025 - 12 - 09 23:15:10.520_830).assume_utc()),
185+
created: datetime!(2025 - 12 - 09 23:14:22.482_359).assume_utc(),
186+
modified: datetime!(2025 - 12 - 09 23:15:10.522_040).assume_utc(),
187+
key_version: 1,
188+
};
189+
assert_eq!(view, should_be);
190+
}
191+
192+
#[test]
193+
fn view_one_missing_last_used() {
194+
let json = r#"
195+
{
196+
"id": 1,
197+
"accountId": 1,
198+
"status": "ACTIVE",
199+
"expiresAt": "2025-12-11T00:00:00Z",
200+
"created": "2025-12-09T23:14:22.482359Z",
201+
"modified": "2025-12-09T23:15:10.522040Z",
202+
"keyVersion": 1
203+
}"#;
204+
let view: ViewOne = serde_json::from_str(json).unwrap();
205+
let should_be = ViewOne {
206+
id: 1,
207+
account_id: 1,
208+
status: Status::Active,
209+
expires_at: datetime!(2025 - 12 - 11 00:00:00).assume_utc(),
210+
last_used_at: None,
211+
created: datetime!(2025 - 12 - 09 23:14:22.482_359).assume_utc(),
212+
modified: datetime!(2025 - 12 - 09 23:15:10.522_040).assume_utc(),
213+
key_version: 1,
214+
};
215+
assert_eq!(view, should_be);
216+
}
217+
218+
#[test]
219+
fn regenerate_response() {
220+
let json = r#"{
221+
"licenseId": 1,
222+
"accountId": 1,
223+
"expiresAt": "2025-12-11T00:00:00Z",
224+
"licenseKey": "foobar"
225+
}"#;
226+
let regenerate: RegenerateResponse = serde_json::from_str(json).unwrap();
227+
let should_be = RegenerateResponse {
228+
account_id: 1,
229+
license_id: 1,
230+
expires_at: datetime!(2025 - 12 - 11 00:00:00).assume_utc(),
231+
license_key: String::from("foobar"),
232+
};
233+
assert_eq!(regenerate, should_be);
234+
}
235+
236+
#[test]
237+
fn verify_request() {
238+
let json = r#"{
239+
"licenseKey": "foobar"
240+
}"#;
241+
let verify: Verify = serde_json::from_str(json).unwrap();
242+
let should_be = Verify {
243+
license_key: String::from("foobar"),
244+
};
245+
assert_eq!(verify, 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+
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pub mod account;
44
pub mod azel;
55
pub mod band;
66
pub mod error;
7+
pub mod gateway_licenses;
78
#[cfg(feature = "serde")]
89
pub mod pagination;
910
pub mod satellite;

src/status.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ pub enum Value {
7878
String(String),
7979
}
8080

81-
#[cfg(test)]
81+
#[cfg(all(test, feature = "serde"))]
8282
mod tests {
8383
use super::*;
8484

src/task.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ impl Hateoas for TaskRequest {
230230
}
231231
}
232232

233-
#[cfg(test)]
233+
#[cfg(all(test, feature = "serde"))]
234234
mod tests {
235235
use super::*;
236236

0 commit comments

Comments
 (0)