1use serde::{Deserialize, Serialize};
2use serde_with::{OneOrMany, TimestampSeconds, formats::PreferMany, serde_as};
3
4fn is_false(value: &bool) -> bool {
5 !value
6}
7
8#[serde_with::skip_serializing_none]
9#[serde_as]
10#[derive(Debug, Serialize, Deserialize, Default, Clone)]
11#[serde(default)]
12pub struct Claims {
13 #[serde(default, rename = "root", skip_serializing_if = "String::is_empty")]
16 pub root: String,
17
18 #[serde(default, rename = "put", skip_serializing_if = "Vec::is_empty")]
21 #[serde_as(as = "OneOrMany<_, PreferMany>")]
22 pub publish: Vec<String>,
23
24 #[serde(default, rename = "cluster", skip_serializing_if = "is_false")]
30 pub cluster: bool,
31
32 #[serde(default, rename = "get", skip_serializing_if = "Vec::is_empty")]
36 #[serde_as(as = "OneOrMany<_, PreferMany>")]
37 pub subscribe: Vec<String>,
38
39 #[serde(rename = "exp")]
41 #[serde_as(as = "Option<TimestampSeconds<i64>>")]
42 pub expires: Option<std::time::SystemTime>,
43
44 #[serde(rename = "iat")]
46 #[serde_as(as = "Option<TimestampSeconds<i64>>")]
47 pub issued: Option<std::time::SystemTime>,
48}
49
50impl Claims {
51 pub fn validate(&self) -> crate::Result<()> {
52 if self.publish.is_empty() && self.subscribe.is_empty() {
53 return Err(crate::Error::UselessToken);
54 }
55
56 Ok(())
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 use std::time::{Duration, SystemTime};
65
66 fn create_test_claims() -> Claims {
67 Claims {
68 root: "test-path".to_string(),
69 publish: vec!["test-pub".into()],
70 cluster: false,
71 subscribe: vec!["test-sub".into()],
72 expires: Some(SystemTime::now() + Duration::from_secs(3600)),
73 issued: Some(SystemTime::now()),
74 }
75 }
76
77 #[test]
78 fn test_claims_validation_success() {
79 let claims = create_test_claims();
80 assert!(claims.validate().is_ok());
81 }
82
83 #[test]
84 fn test_claims_validation_no_publish_or_subscribe() {
85 let claims = Claims {
86 root: "test-path".to_string(),
87 publish: vec![],
88 subscribe: vec![],
89 cluster: false,
90 expires: None,
91 issued: None,
92 };
93
94 let result = claims.validate();
95 assert!(result.is_err());
96 assert!(
97 result
98 .unwrap_err()
99 .to_string()
100 .contains("no publish or subscribe allowed; token is useless")
101 );
102 }
103
104 #[test]
105 fn test_claims_validation_only_publish() {
106 let claims = Claims {
107 root: "test-path".to_string(),
108 publish: vec!["test-pub".into()],
109 subscribe: vec![],
110 cluster: false,
111 expires: None,
112 issued: None,
113 };
114
115 assert!(claims.validate().is_ok());
116 }
117
118 #[test]
119 fn test_claims_validation_only_subscribe() {
120 let claims = Claims {
121 root: "test-path".to_string(),
122 publish: vec![],
123 subscribe: vec!["test-sub".into()],
124 cluster: false,
125 expires: None,
126 issued: None,
127 };
128
129 assert!(claims.validate().is_ok());
130 }
131
132 #[test]
133 fn test_claims_validation_path_not_prefix_relative_publish() {
134 let claims = Claims {
135 root: "test-path".to_string(), publish: vec!["relative-pub".into()], subscribe: vec![],
138 cluster: false,
139 expires: None,
140 issued: None,
141 };
142
143 let result = claims.validate();
144 assert!(result.is_ok()); }
146
147 #[test]
148 fn test_claims_validation_path_not_prefix_relative_subscribe() {
149 let claims = Claims {
150 root: "test-path".to_string(), publish: vec![],
152 subscribe: vec!["relative-sub".into()], cluster: false,
154 expires: None,
155 issued: None,
156 };
157
158 let result = claims.validate();
159 assert!(result.is_ok()); }
161
162 #[test]
163 fn test_claims_validation_path_not_prefix_absolute_publish() {
164 let claims = Claims {
165 root: "test-path".to_string(), publish: vec!["/absolute-pub".into()], subscribe: vec![],
168 cluster: false,
169 expires: None,
170 issued: None,
171 };
172
173 assert!(claims.validate().is_ok());
174 }
175
176 #[test]
177 fn test_claims_validation_path_not_prefix_absolute_subscribe() {
178 let claims = Claims {
179 root: "test-path".to_string(), publish: vec![],
181 subscribe: vec!["/absolute-sub".into()], cluster: false,
183 expires: None,
184 issued: None,
185 };
186
187 assert!(claims.validate().is_ok());
188 }
189
190 #[test]
191 fn test_claims_validation_path_not_prefix_empty_publish() {
192 let claims = Claims {
193 root: "test-path".to_string(), publish: vec!["".into()], subscribe: vec![],
196 cluster: false,
197 expires: None,
198 issued: None,
199 };
200
201 assert!(claims.validate().is_ok());
202 }
203
204 #[test]
205 fn test_claims_validation_path_not_prefix_empty_subscribe() {
206 let claims = Claims {
207 root: "test-path".to_string(), publish: vec![],
209 subscribe: vec!["".into()], cluster: false,
211 expires: None,
212 issued: None,
213 };
214
215 assert!(claims.validate().is_ok());
216 }
217
218 #[test]
219 fn test_claims_validation_path_is_prefix() {
220 let claims = Claims {
221 root: "test-path".to_string(), publish: vec!["relative-pub".into()], subscribe: vec!["relative-sub".into()], cluster: false,
225 expires: None,
226 issued: None,
227 };
228
229 assert!(claims.validate().is_ok());
230 }
231
232 #[test]
233 fn test_claims_validation_empty_path() {
234 let claims = Claims {
235 root: "".to_string(), publish: vec!["test-pub".into()],
237 subscribe: vec![],
238 cluster: false,
239 expires: None,
240 issued: None,
241 };
242
243 assert!(claims.validate().is_ok());
244 }
245
246 #[test]
247 fn test_claims_serde() {
248 let claims = create_test_claims();
249 let json = serde_json::to_string(&claims).unwrap();
250 let deserialized: Claims = serde_json::from_str(&json).unwrap();
251
252 assert_eq!(deserialized.root, claims.root);
253 assert_eq!(deserialized.publish, claims.publish);
254 assert_eq!(deserialized.subscribe, claims.subscribe);
255 assert_eq!(deserialized.cluster, claims.cluster);
256 }
257
258 #[test]
259 fn test_claims_default() {
260 let claims = Claims::default();
261 assert_eq!(claims.root, "");
262 assert!(claims.publish.is_empty());
263 assert!(claims.subscribe.is_empty());
264 assert!(!claims.cluster);
265 assert_eq!(claims.expires, None);
266 assert_eq!(claims.issued, None);
267 }
268
269 #[test]
270 fn test_is_false_helper() {
271 assert!(is_false(&false));
272 assert!(!is_false(&true));
273 }
274
275 #[test]
276 fn test_deserialize_string_as_vec() {
277 let json = r#"{
278 "root": "test",
279 "put": "single-publish",
280 "get": "single-subscribe"
281 }"#;
282
283 let claims: Claims = serde_json::from_str(json).unwrap();
284 assert_eq!(claims.publish, vec!["single-publish"]);
285 assert_eq!(claims.subscribe, vec!["single-subscribe"]);
286 }
287
288 #[test]
289 fn test_deserialize_vec_as_vec() {
290 let json = r#"{
291 "root": "test",
292 "put": ["pub1", "pub2"],
293 "get": ["sub1", "sub2"]
294 }"#;
295
296 let claims: Claims = serde_json::from_str(json).unwrap();
297 assert_eq!(claims.publish, vec!["pub1", "pub2"]);
298 assert_eq!(claims.subscribe, vec!["sub1", "sub2"]);
299 }
300
301 #[test]
302 fn test_deserialize_mixed() {
303 let json = r#"{
304 "root": "test",
305 "put": "single",
306 "get": ["multi1", "multi2"]
307 }"#;
308
309 let claims: Claims = serde_json::from_str(json).unwrap();
310 assert_eq!(claims.publish, vec!["single"]);
311 assert_eq!(claims.subscribe, vec!["multi1", "multi2"]);
312 }
313}