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