]>
Commit | Line | Data |
---|---|---|
4ebf0eab | 1 | use failure::*; |
fc189b19 | 2 | use ::serde::{Deserialize, Serialize}; |
4ebf0eab | 3 | |
9ea4bce4 WB |
4 | use proxmox::api::{api, schema::*}; |
5 | use proxmox::const_regex; | |
6 | use proxmox::{IPRE, IPV4RE, IPV6RE, IPV4OCTET, IPV6H16, IPV6LS32}; | |
255f378a DM |
7 | |
8 | // File names: may not contain slashes, may not start with "." | |
9 | pub const FILENAME_FORMAT: ApiStringFormat = ApiStringFormat::VerifyFn(|name| { | |
10 | if name.starts_with('.') { | |
11 | bail!("file names may not start with '.'"); | |
12 | } | |
13 | if name.contains('/') { | |
14 | bail!("file names may not contain slashes"); | |
15 | } | |
16 | Ok(()) | |
17 | }); | |
18 | ||
b25f313d DM |
19 | macro_rules! DNS_LABEL { () => (r"(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)") } |
20 | macro_rules! DNS_NAME { () => (concat!(r"(?:", DNS_LABEL!() , r"\.)*", DNS_LABEL!())) } | |
255f378a | 21 | |
163dc16c DM |
22 | // we only allow a limited set of characters |
23 | // colon is not allowed, because we store usernames in | |
24 | // colon separated lists)! | |
25 | // slash is not allowed because it is used as pve API delimiter | |
26 | // also see "man useradd" | |
ae62c4fe | 27 | macro_rules! USER_NAME_REGEX_STR { () => (r"(?:[^\s:/[:cntrl:]]+)") } |
163dc16c DM |
28 | |
29 | macro_rules! PROXMOX_SAFE_ID_REGEX_STR { () => (r"(?:[A-Za-z0-9_][A-Za-z0-9._\-]*)") } | |
30 | ||
255f378a DM |
31 | const_regex!{ |
32 | pub IP_FORMAT_REGEX = IPRE!(); | |
33 | pub SHA256_HEX_REGEX = r"^[a-f0-9]{64}$"; // fixme: define in common_regex ? | |
34 | pub SYSTEMD_DATETIME_REGEX = r"^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$"; // fixme: define in common_regex ? | |
d0adf270 | 35 | |
da4a15a3 DM |
36 | pub PASSWORD_REGEX = r"^[[:^cntrl:]]*$"; // everything but control characters |
37 | ||
d0adf270 DM |
38 | /// Regex for safe identifiers. |
39 | /// | |
40 | /// This | |
41 | /// [article](https://dwheeler.com/essays/fixing-unix-linux-filenames.html) | |
42 | /// contains further information why it is reasonable to restict | |
43 | /// names this way. This is not only useful for filenames, but for | |
44 | /// any identifier command line tools work with. | |
163dc16c | 45 | pub PROXMOX_SAFE_ID_REGEX = concat!(r"^", PROXMOX_SAFE_ID_REGEX_STR!(), r"$"); |
454c13ed DM |
46 | |
47 | pub SINGLE_LINE_COMMENT_REGEX = r"^[[:^cntrl:]]*$"; | |
b25f313d DM |
48 | |
49 | pub HOSTNAME_REGEX = r"^(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)$"; | |
50 | ||
ae62c4fe | 51 | pub DNS_NAME_REGEX = concat!(r"^", DNS_NAME!(), r"$"); |
b25f313d | 52 | |
ae62c4fe | 53 | pub DNS_NAME_OR_IP_REGEX = concat!(r"^", DNS_NAME!(), "|", IPRE!(), r"$"); |
163dc16c DM |
54 | |
55 | pub PROXMOX_USER_ID_REGEX = concat!(r"^", USER_NAME_REGEX_STR!(), r"@", PROXMOX_SAFE_ID_REGEX_STR!(), r"$"); | |
dcb8db66 DM |
56 | |
57 | pub CERT_FINGERPRINT_SHA256_REGEX = r"^(?:[0-9a-fA-F][0-9a-fA-F])(?::[0-9a-fA-F][0-9a-fA-F]){31}$"; | |
ed3e60ae DM |
58 | |
59 | pub ACL_PATH_REGEX = concat!(r"^(?:\/|", r"(?:\/", PROXMOX_SAFE_ID_REGEX_STR!(), ")+", r")$"); | |
255f378a | 60 | } |
4ebf0eab | 61 | |
255f378a DM |
62 | pub const SYSTEMD_DATETIME_FORMAT: ApiStringFormat = |
63 | ApiStringFormat::Pattern(&SYSTEMD_DATETIME_REGEX); | |
4ebf0eab | 64 | |
255f378a DM |
65 | pub const IP_FORMAT: ApiStringFormat = |
66 | ApiStringFormat::Pattern(&IP_FORMAT_REGEX); | |
bbf9e7e9 | 67 | |
255f378a DM |
68 | pub const PVE_CONFIG_DIGEST_FORMAT: ApiStringFormat = |
69 | ApiStringFormat::Pattern(&SHA256_HEX_REGEX); | |
70 | ||
dcb8db66 DM |
71 | pub const CERT_FINGERPRINT_SHA256_FORMAT: ApiStringFormat = |
72 | ApiStringFormat::Pattern(&CERT_FINGERPRINT_SHA256_REGEX); | |
73 | ||
d0adf270 DM |
74 | pub const PROXMOX_SAFE_ID_FORMAT: ApiStringFormat = |
75 | ApiStringFormat::Pattern(&PROXMOX_SAFE_ID_REGEX); | |
76 | ||
454c13ed DM |
77 | pub const SINGLE_LINE_COMMENT_FORMAT: ApiStringFormat = |
78 | ApiStringFormat::Pattern(&SINGLE_LINE_COMMENT_REGEX); | |
79 | ||
b25f313d DM |
80 | pub const HOSTNAME_FORMAT: ApiStringFormat = |
81 | ApiStringFormat::Pattern(&HOSTNAME_REGEX); | |
82 | ||
83 | pub const DNS_NAME_FORMAT: ApiStringFormat = | |
84 | ApiStringFormat::Pattern(&DNS_NAME_REGEX); | |
85 | ||
86 | pub const DNS_NAME_OR_IP_FORMAT: ApiStringFormat = | |
87 | ApiStringFormat::Pattern(&DNS_NAME_OR_IP_REGEX); | |
88 | ||
163dc16c DM |
89 | pub const PROXMOX_USER_ID_FORMAT: ApiStringFormat = |
90 | ApiStringFormat::Pattern(&PROXMOX_USER_ID_REGEX); | |
91 | ||
7e7b781a DM |
92 | pub const PASSWORD_FORMAT: ApiStringFormat = |
93 | ApiStringFormat::Pattern(&PASSWORD_REGEX); | |
94 | ||
ed3e60ae DM |
95 | pub const ACL_PATH_FORMAT: ApiStringFormat = |
96 | ApiStringFormat::Pattern(&ACL_PATH_REGEX); | |
97 | ||
454c13ed | 98 | |
685e1334 DM |
99 | pub const PASSWORD_SCHEMA: Schema = StringSchema::new("Password.") |
100 | .format(&PASSWORD_FORMAT) | |
101 | .min_length(1) | |
b88f9c5b | 102 | .max_length(1024) |
685e1334 DM |
103 | .schema(); |
104 | ||
105 | pub const PBS_PASSWORD_SCHEMA: Schema = StringSchema::new("User Password.") | |
106 | .format(&PASSWORD_FORMAT) | |
107 | .min_length(5) | |
108 | .max_length(64) | |
109 | .schema(); | |
dcb8db66 DM |
110 | |
111 | pub const CERT_FINGERPRINT_SHA256_SCHEMA: Schema = StringSchema::new( | |
112 | "X509 certificate fingerprint (sha256)." | |
113 | ) | |
114 | .format(&CERT_FINGERPRINT_SHA256_FORMAT) | |
115 | .schema(); | |
116 | ||
002a191a | 117 | pub const PROXMOX_CONFIG_DIGEST_SCHEMA: Schema = StringSchema::new(r#"\ |
255f378a DM |
118 | Prevent changes if current configuration file has different SHA256 digest. |
119 | This can be used to prevent concurrent modifications. | |
120 | "# | |
121 | ) | |
122 | .format(&PVE_CONFIG_DIGEST_FORMAT) | |
123 | .schema(); | |
124 | ||
125 | ||
126 | pub const CHUNK_DIGEST_FORMAT: ApiStringFormat = | |
127 | ApiStringFormat::Pattern(&SHA256_HEX_REGEX); | |
128 | ||
129 | pub const CHUNK_DIGEST_SCHEMA: Schema = StringSchema::new("Chunk digest (SHA256).") | |
130 | .format(&CHUNK_DIGEST_FORMAT) | |
131 | .schema(); | |
132 | ||
133 | pub const NODE_SCHEMA: Schema = StringSchema::new("Node name (or 'localhost')") | |
134 | .format(&ApiStringFormat::VerifyFn(|node| { | |
135 | if node == "localhost" || node == proxmox::tools::nodename() { | |
136 | Ok(()) | |
137 | } else { | |
138 | bail!("no such node '{}'", node); | |
139 | } | |
140 | })) | |
141 | .schema(); | |
142 | ||
143 | pub const SEARCH_DOMAIN_SCHEMA: Schema = | |
144 | StringSchema::new("Search domain for host-name lookup.").schema(); | |
145 | ||
146 | pub const FIRST_DNS_SERVER_SCHEMA: Schema = | |
147 | StringSchema::new("First name server IP address.") | |
148 | .format(&IP_FORMAT) | |
149 | .schema(); | |
150 | ||
151 | pub const SECOND_DNS_SERVER_SCHEMA: Schema = | |
152 | StringSchema::new("Second name server IP address.") | |
153 | .format(&IP_FORMAT) | |
154 | .schema(); | |
155 | ||
156 | pub const THIRD_DNS_SERVER_SCHEMA: Schema = | |
157 | StringSchema::new("Third name server IP address.") | |
158 | .format(&IP_FORMAT) | |
159 | .schema(); | |
160 | ||
161 | pub const BACKUP_ARCHIVE_NAME_SCHEMA: Schema = | |
162 | StringSchema::new("Backup archive name.") | |
1ae5677d | 163 | .format(&PROXMOX_SAFE_ID_FORMAT) |
255f378a DM |
164 | .schema(); |
165 | ||
166 | pub const BACKUP_TYPE_SCHEMA: Schema = | |
167 | StringSchema::new("Backup type.") | |
168 | .format(&ApiStringFormat::Enum(&["vm", "ct", "host"])) | |
169 | .schema(); | |
170 | ||
171 | pub const BACKUP_ID_SCHEMA: Schema = | |
172 | StringSchema::new("Backup ID.") | |
1ae5677d | 173 | .format(&PROXMOX_SAFE_ID_FORMAT) |
255f378a DM |
174 | .schema(); |
175 | ||
176 | pub const BACKUP_TIME_SCHEMA: Schema = | |
177 | IntegerSchema::new("Backup time (Unix epoch.)") | |
178 | .minimum(1_547_797_308) | |
179 | .schema(); | |
5830c205 DM |
180 | |
181 | pub const UPID_SCHEMA: Schema = StringSchema::new("Unique Process/Task ID.") | |
182 | .max_length(256) | |
183 | .schema(); | |
66c49c21 DM |
184 | |
185 | pub const DATASTORE_SCHEMA: Schema = StringSchema::new("Datastore name.") | |
d0adf270 | 186 | .format(&PROXMOX_SAFE_ID_FORMAT) |
688fbe07 | 187 | .min_length(3) |
66c49c21 DM |
188 | .max_length(32) |
189 | .schema(); | |
fc189b19 | 190 | |
167971ed DM |
191 | pub const REMOTE_ID_SCHEMA: Schema = StringSchema::new("Remote ID.") |
192 | .format(&PROXMOX_SAFE_ID_FORMAT) | |
193 | .min_length(3) | |
194 | .max_length(32) | |
195 | .schema(); | |
196 | ||
454c13ed DM |
197 | pub const SINGLE_LINE_COMMENT_SCHEMA: Schema = StringSchema::new("Comment (single line).") |
198 | .format(&SINGLE_LINE_COMMENT_FORMAT) | |
199 | .schema(); | |
fc189b19 | 200 | |
b25f313d DM |
201 | pub const HOSTNAME_SCHEMA: Schema = StringSchema::new("Hostname (as defined in RFC1123).") |
202 | .format(&HOSTNAME_FORMAT) | |
203 | .schema(); | |
204 | ||
205 | pub const DNS_NAME_OR_IP_SCHEMA: Schema = StringSchema::new("DNS name or IP address.") | |
206 | .format(&DNS_NAME_OR_IP_FORMAT) | |
207 | .schema(); | |
208 | ||
163dc16c DM |
209 | pub const PROXMOX_AUTH_REALM_SCHEMA: Schema = StringSchema::new("Authentication domain ID") |
210 | .format(&PROXMOX_SAFE_ID_FORMAT) | |
211 | .min_length(3) | |
212 | .max_length(32) | |
213 | .schema(); | |
214 | ||
215 | pub const PROXMOX_USER_ID_SCHEMA: Schema = StringSchema::new("User ID") | |
216 | .format(&PROXMOX_USER_ID_FORMAT) | |
217 | .min_length(3) | |
218 | .max_length(64) | |
219 | .schema(); | |
220 | ||
fc189b19 DM |
221 | |
222 | // Complex type definitions | |
223 | ||
b31c8019 DM |
224 | #[api( |
225 | properties: { | |
226 | "backup-type": { | |
227 | schema: BACKUP_TYPE_SCHEMA, | |
228 | }, | |
229 | "backup-id": { | |
230 | schema: BACKUP_ID_SCHEMA, | |
231 | }, | |
232 | "last-backup": { | |
233 | schema: BACKUP_TIME_SCHEMA, | |
234 | }, | |
235 | "backup-count": { | |
236 | type: Integer, | |
237 | }, | |
238 | files: { | |
239 | items: { | |
240 | schema: BACKUP_ARCHIVE_NAME_SCHEMA | |
241 | }, | |
242 | }, | |
243 | }, | |
244 | )] | |
245 | #[derive(Serialize, Deserialize)] | |
246 | #[serde(rename_all="kebab-case")] | |
247 | /// Basic information about a backup group. | |
248 | pub struct GroupListItem { | |
249 | pub backup_type: String, // enum | |
250 | pub backup_id: String, | |
251 | pub last_backup: i64, | |
252 | /// Number of contained snapshots | |
253 | pub backup_count: u64, | |
254 | /// List of contained archive files. | |
255 | pub files: Vec<String>, | |
256 | } | |
257 | ||
fc189b19 | 258 | #[api( |
fc189b19 DM |
259 | properties: { |
260 | "backup-type": { | |
261 | schema: BACKUP_TYPE_SCHEMA, | |
262 | }, | |
263 | "backup-id": { | |
264 | schema: BACKUP_ID_SCHEMA, | |
265 | }, | |
266 | "backup-time": { | |
267 | schema: BACKUP_TIME_SCHEMA, | |
268 | }, | |
71da3d6a DM |
269 | files: { |
270 | items: { | |
271 | schema: BACKUP_ARCHIVE_NAME_SCHEMA | |
272 | }, | |
273 | }, | |
fc189b19 DM |
274 | }, |
275 | )] | |
276 | #[derive(Serialize, Deserialize)] | |
277 | #[serde(rename_all="kebab-case")] | |
71da3d6a | 278 | /// Basic information about backup snapshot. |
fc189b19 DM |
279 | pub struct SnapshotListItem { |
280 | pub backup_type: String, // enum | |
281 | pub backup_id: String, | |
282 | pub backup_time: i64, | |
71da3d6a | 283 | /// List of contained archive files. |
fc189b19 | 284 | pub files: Vec<String>, |
71da3d6a | 285 | /// Overall snapshot size (sum of all archive sizes). |
fc189b19 DM |
286 | #[serde(skip_serializing_if="Option::is_none")] |
287 | pub size: Option<u64>, | |
288 | } | |
ff620a3d | 289 | |
09b1f7b2 DM |
290 | #[api( |
291 | properties: { | |
292 | "filename": { | |
293 | schema: BACKUP_ARCHIVE_NAME_SCHEMA, | |
294 | }, | |
295 | }, | |
296 | )] | |
297 | #[derive(Serialize, Deserialize)] | |
298 | #[serde(rename_all="kebab-case")] | |
299 | /// Basic information about archive files inside a backup snapshot. | |
300 | pub struct BackupContent { | |
301 | pub filename: String, | |
302 | /// Archive size (from backup manifest). | |
303 | #[serde(skip_serializing_if="Option::is_none")] | |
304 | pub size: Option<u64>, | |
305 | } | |
306 | ||
a92830dc DM |
307 | #[api( |
308 | properties: { | |
309 | "upid": { | |
310 | optional: true, | |
311 | schema: UPID_SCHEMA, | |
312 | }, | |
313 | }, | |
314 | )] | |
315 | #[derive(Clone, Serialize, Deserialize)] | |
316 | #[serde(rename_all="kebab-case")] | |
317 | /// Garbage collection status. | |
318 | pub struct GarbageCollectionStatus { | |
319 | pub upid: Option<String>, | |
320 | /// Number of processed index files. | |
321 | pub index_file_count: usize, | |
322 | /// Sum of bytes referred by index files. | |
323 | pub index_data_bytes: u64, | |
324 | /// Bytes used on disk. | |
325 | pub disk_bytes: u64, | |
326 | /// Chunks used on disk. | |
327 | pub disk_chunks: usize, | |
328 | /// Sum of removed bytes. | |
329 | pub removed_bytes: u64, | |
330 | /// Number of removed chunks. | |
331 | pub removed_chunks: usize, | |
cf459b19 DM |
332 | /// Sum of pending bytes (pending removal - kept for safety). |
333 | pub pending_bytes: u64, | |
334 | /// Number of pending chunks (pending removal - kept for safety). | |
335 | pub pending_chunks: usize, | |
a92830dc DM |
336 | } |
337 | ||
338 | impl Default for GarbageCollectionStatus { | |
339 | fn default() -> Self { | |
340 | GarbageCollectionStatus { | |
341 | upid: None, | |
342 | index_file_count: 0, | |
343 | index_data_bytes: 0, | |
344 | disk_bytes: 0, | |
345 | disk_chunks: 0, | |
346 | removed_bytes: 0, | |
347 | removed_chunks: 0, | |
cf459b19 DM |
348 | pending_bytes: 0, |
349 | pending_chunks: 0, | |
a92830dc DM |
350 | } |
351 | } | |
352 | } | |
353 | ||
354 | ||
1dc117bb DM |
355 | #[api()] |
356 | #[derive(Serialize, Deserialize)] | |
357 | /// Storage space usage information. | |
358 | pub struct StorageStatus { | |
359 | /// Total space (bytes). | |
360 | pub total: u64, | |
361 | /// Used space (bytes). | |
362 | pub used: u64, | |
363 | /// Available space (bytes). | |
364 | pub avail: u64, | |
365 | } | |
ff620a3d | 366 | |
99384f79 DM |
367 | #[api( |
368 | properties: { | |
369 | "upid": { schema: UPID_SCHEMA }, | |
370 | }, | |
371 | )] | |
372 | #[derive(Serialize, Deserialize)] | |
373 | /// Task properties. | |
374 | pub struct TaskListItem { | |
375 | pub upid: String, | |
376 | /// The node name where the task is running on. | |
377 | pub node: String, | |
378 | /// The Unix PID | |
379 | pub pid: i64, | |
380 | /// The task start time (Epoch) | |
381 | pub pstart: u64, | |
382 | /// The task start time (Epoch) | |
383 | pub starttime: i64, | |
384 | /// Worker type (arbitrary ASCII string) | |
385 | pub worker_type: String, | |
386 | /// Worker ID (arbitrary ASCII string) | |
387 | pub worker_id: Option<String>, | |
388 | /// The user who started the task | |
389 | pub user: String, | |
390 | /// The task end time (Epoch) | |
391 | #[serde(skip_serializing_if="Option::is_none")] | |
392 | pub endtime: Option<i64>, | |
393 | /// Task end status | |
394 | #[serde(skip_serializing_if="Option::is_none")] | |
395 | pub status: Option<String>, | |
396 | } | |
397 | ||
ff620a3d DM |
398 | // Regression tests |
399 | ||
dcb8db66 DM |
400 | #[test] |
401 | fn test_cert_fingerprint_schema() -> Result<(), Error> { | |
402 | ||
403 | let schema = CERT_FINGERPRINT_SHA256_SCHEMA; | |
404 | ||
405 | let invalid_fingerprints = [ | |
406 | "86:88:7c:be:26:77:a5:62:67:d9:06:f5:e4::61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8", | |
407 | "88:7C:BE:26:77:a5:62:67:D9:06:f5:e4:14:61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8", | |
408 | "86:88:7c:be:26:77:a5:62:67:d9:06:f5:e4::14:61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8:ff", | |
409 | "XX:88:7c:be:26:77:a5:62:67:d9:06:f5:e4::14:61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8", | |
410 | "86:88:Y4:be:26:77:a5:62:67:d9:06:f5:e4:14:61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8", | |
411 | "86:88:0:be:26:77:a5:62:67:d9:06:f5:e4:14:61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8", | |
412 | ]; | |
413 | ||
414 | for fingerprint in invalid_fingerprints.iter() { | |
415 | if let Ok(_) = parse_simple_value(fingerprint, &schema) { | |
416 | bail!("test fingerprint '{}' failed - got Ok() while expection an error.", fingerprint); | |
417 | } | |
418 | } | |
419 | ||
420 | let valid_fingerprints = [ | |
421 | "86:88:7c:be:26:77:a5:62:67:d9:06:f5:e4:14:61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8", | |
422 | "86:88:7C:BE:26:77:a5:62:67:D9:06:f5:e4:14:61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8", | |
423 | ]; | |
424 | ||
425 | for fingerprint in valid_fingerprints.iter() { | |
426 | let v = match parse_simple_value(fingerprint, &schema) { | |
427 | Ok(v) => v, | |
428 | Err(err) => { | |
429 | bail!("unable to parse fingerprint '{}' - {}", fingerprint, err); | |
430 | } | |
431 | }; | |
432 | ||
433 | if v != serde_json::json!(fingerprint) { | |
434 | bail!("unable to parse fingerprint '{}' - got wrong value {:?}", fingerprint, v); | |
435 | } | |
436 | } | |
437 | ||
438 | Ok(()) | |
439 | } | |
440 | ||
ff620a3d DM |
441 | #[test] |
442 | fn test_proxmox_user_id_schema() -> Result<(), Error> { | |
443 | ||
444 | let schema = PROXMOX_USER_ID_SCHEMA; | |
445 | ||
446 | let invalid_user_ids = [ | |
447 | "x", // too short | |
448 | "xx", // too short | |
449 | "xxx", // no realm | |
450 | "xxx@", // no realm | |
451 | "xx x@test", // contains space | |
452 | "xx\nx@test", // contains control character | |
453 | "x:xx@test", // contains collon | |
454 | "xx/x@test", // contains slash | |
455 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@test", // too long | |
456 | ]; | |
457 | ||
458 | for name in invalid_user_ids.iter() { | |
459 | if let Ok(_) = parse_simple_value(name, &schema) { | |
460 | bail!("test userid '{}' failed - got Ok() while expection an error.", name); | |
461 | } | |
462 | } | |
463 | ||
464 | let valid_user_ids = [ | |
465 | "xxx@y", | |
466 | "name@y", | |
467 | "xxx@test-it.com", | |
468 | "xxx@_T_E_S_T-it.com", | |
469 | "x_x-x.x@test-it.com", | |
470 | ]; | |
471 | ||
472 | for name in valid_user_ids.iter() { | |
473 | let v = match parse_simple_value(name, &schema) { | |
474 | Ok(v) => v, | |
475 | Err(err) => { | |
476 | bail!("unable to parse userid '{}' - {}", name, err); | |
477 | } | |
478 | }; | |
479 | ||
480 | if v != serde_json::json!(name) { | |
481 | bail!("unable to parse userid '{}' - got wrong value {:?}", name, v); | |
482 | } | |
483 | } | |
484 | ||
485 | Ok(()) | |
486 | } |