1use serde::de::DeserializeOwned;
2
3pub trait Projection: DeserializeOwned {
5 const FIELDS: &'static str;
6}
7
8impl<T: Projection> Projection for Vec<T> {
9 const FIELDS: &'static str = T::FIELDS;
10}
11
12pub fn check_selector(selector: &str) -> std::result::Result<(), String> {
14 let mut depth = 0usize;
15 let mut name = String::new();
16 let mut previous = ' ';
17 for c in selector.chars() {
18 match c {
19 ',' | '(' => {
20 if name.is_empty() && !(c == ',' && previous == ')') {
21 return Err(format!("empty name before '{c}' in '{selector}'"));
22 }
23 if c == '(' {
24 depth += 1;
25 }
26 name.clear();
27 }
28 ')' => {
29 if name.is_empty() && previous != ')' {
30 return Err(format!("empty name before ')' in '{selector}'"));
31 }
32 depth = depth
33 .checked_sub(1)
34 .ok_or_else(|| format!("unbalanced ')' in '{selector}'"))?;
35 name.clear();
36 }
37 c if c.is_ascii_alphanumeric() || c == '$' || c == '_' => name.push(c),
38 other => return Err(format!("unexpected '{other}' in '{selector}'")),
39 }
40 previous = c;
41 }
42 if depth != 0 {
43 return Err(format!("unbalanced '(' in '{selector}'"));
44 }
45 if name.is_empty() && previous != ')' {
46 return Err(format!("empty name at the end of '{selector}'"));
47 }
48 Ok(())
49}
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54
55 #[test]
56 fn valid_selectors() {
57 for selector in [
58 "idReadable",
59 "id,login,name",
60 "project(shortName,name),customFields(name,$type,value(name,login))",
61 ] {
62 assert_eq!(check_selector(selector), Ok(()), "{selector}");
63 }
64 }
65
66 #[test]
67 fn invalid_selectors() {
68 for selector in [
69 "", "id,,name", "project(", "project)", "a(b))", "a b", "(name)", "a,",
70 ] {
71 assert!(check_selector(selector).is_err(), "{selector}");
72 }
73 }
74
75 #[test]
76 fn me_selector_is_valid() {
77 assert_eq!(
78 check_selector(<crate::api::users::Me as Projection>::FIELDS),
79 Ok(())
80 );
81 }
82}