1use fidl_fuchsia_diagnostics::{Selector, StringSelector, TreeNames};
6use selectors::FastError;
7use serde::{de, Deserialize, Deserializer};
8use std::fmt;
9use std::marker::PhantomData;
10use std::sync::LazyLock;
11use thiserror::Error;
12
13pub fn greater_than_zero<'de, D>(deserializer: D) -> Result<i64, D::Error>
14where
15 D: Deserializer<'de>,
16{
17 let value = i64::deserialize(deserializer)?;
18 if value <= 0 {
19 return Err(de::Error::custom(format!("i64 out of range: {value}")));
20 }
21 Ok(value)
22}
23
24pub fn one_or_many_selectors<'de, D>(deserializer: D) -> Result<Vec<Selector>, D::Error>
25where
26 D: Deserializer<'de>,
27{
28 deserializer.deserialize_any(OneOrMany(PhantomData::<Selector>))
29}
30
31pub(crate) struct OneOrMany<T>(pub PhantomData<T>);
32
33trait ParseString {
34 fn parse_string(value: &str) -> Result<Self, Error>
35 where
36 Self: Sized;
37}
38
39impl ParseString for String {
40 fn parse_string(value: &str) -> Result<Self, Error> {
41 Ok(value.into())
42 }
43}
44
45impl ParseString for Selector {
46 fn parse_string(value: &str) -> Result<Self, Error> {
47 parse_selector(value)
48 }
49}
50
51impl<'de, T> de::Visitor<'de> for OneOrMany<T>
52where
53 T: ParseString,
54{
55 type Value = Vec<T>;
56
57 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 f.write_str("either a single string or an array of strings")
59 }
60
61 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
62 where
63 E: de::Error,
64 {
65 let result = T::parse_string(value).map_err(E::custom)?;
66 Ok(vec![result])
67 }
68
69 fn visit_seq<S>(self, mut visitor: S) -> Result<Self::Value, S::Error>
70 where
71 S: de::SeqAccess<'de>,
72 {
73 let mut out = vec![];
74 while let Some(s) = visitor.next_element::<String>()? {
75 use serde::de::Error;
76 let selector = T::parse_string(&s).map_err(S::Error::custom)?;
77 out.push(selector);
78 }
79 if out.is_empty() {
80 Err(de::Error::invalid_length(0, &"expected at least one string"))
81 } else {
82 Ok(out)
83 }
84 }
85}
86
87pub fn parse_selector(selector_str: &str) -> Result<Selector, Error> {
88 let selector = selectors::parse_selector::<FastError>(selector_str)?;
89 verify_wildcard_restrictions(&selector, selector_str)?;
90 Ok(selector)
91}
92
93struct WildcardRestriction {
94 segments: Vec<StringSelector>,
95 must_have_tree_name: bool,
96}
97
98static WILDCARD_RESTRICTIONS: LazyLock<Vec<WildcardRestriction>> = LazyLock::new(|| {
99 vec![
100 WildcardRestriction {
101 segments: vec![
102 StringSelector::ExactMatch("core".into()),
103 StringSelector::ExactMatch("bluetooth-core".into()),
104 StringSelector::StringPattern("bt-host-collection:bt-host_*".into()),
105 ],
106 must_have_tree_name: false,
107 },
108 WildcardRestriction {
109 segments: vec![
110 StringSelector::ExactMatch("bootstrap".into()),
111 StringSelector::StringPattern("*-drivers:*".into()),
112 ],
113 must_have_tree_name: true,
114 },
115 WildcardRestriction {
116 segments: vec![
117 StringSelector::ExactMatch("bootstrap".into()),
118 StringSelector::ExactMatch("fshost".into()),
119 StringSelector::ExactMatch("fvm2".into()),
120 StringSelector::StringPattern("blobfs-collection:*".into()),
121 ],
122 must_have_tree_name: false,
123 },
124 WildcardRestriction {
125 segments: vec![
126 StringSelector::ExactMatch("bootstrap".into()),
127 StringSelector::ExactMatch("fshost".into()),
128 StringSelector::ExactMatch("fvm2".into()),
129 StringSelector::StringPattern("fs-collection:*".into()),
130 ],
131 must_have_tree_name: false,
132 },
133 ]
134});
135
136fn verify_wildcard_restrictions(selector: &Selector, raw_selector: &str) -> Result<(), Error> {
138 let actual_segments =
141 selector.component_selector.as_ref().unwrap().moniker_segments.as_ref().unwrap();
142 if !actual_segments.iter().any(|segment| matches!(segment, StringSelector::StringPattern(_))) {
143 return Ok(());
144 }
145 for restriction in &*WILDCARD_RESTRICTIONS {
146 if restriction.segments.len() != actual_segments.len() {
147 continue;
148 }
149 if restriction
150 .segments
151 .iter()
152 .zip(actual_segments.iter())
153 .any(|(expected_segment, actual_segment)| expected_segment != actual_segment)
154 {
155 continue;
156 }
157 if restriction.must_have_tree_name {
158 let Some(TreeNames::Some(_)) = selector.tree_names else {
159 return Err(Error::InvalidWildcardedSelector(raw_selector.to_string()));
160 };
161 }
162 return Ok(());
163 }
164 Err(Error::InvalidWildcardedSelector(raw_selector.to_string()))
165}
166
167#[derive(Debug, Error)]
168pub enum Error {
169 #[error("wildcarded component not allowlisted: '{0}'")]
170 InvalidWildcardedSelector(String),
171
172 #[error(transparent)]
173 ParseSelector(#[from] selectors::Error),
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179 use assert_matches::assert_matches;
180
181 #[derive(Debug, Deserialize, PartialEq)]
182 struct Test(#[serde(deserialize_with = "super::one_or_many_selectors")] Vec<Selector>);
183
184 #[derive(Debug, Deserialize, Eq, PartialEq)]
185 struct TestInt(#[serde(deserialize_with = "super::greater_than_zero")] i64);
186
187 #[fuchsia::test]
188 fn parse_valid_single_selector() {
189 let json = "\"core/bar:root/twig:leaf\"";
190 let data: Test = serde_json5::from_str(json).expect("deserialize");
191 assert_eq!(
192 data,
193 Test(vec![selectors::parse_selector::<FastError>("core/bar:root/twig:leaf").unwrap()])
194 );
195 }
196
197 #[fuchsia::test]
198 fn parse_valid_multiple_selectors() {
199 let json = "[ \"core/foo:some/branch:leaf\", \"core/bar:root/twig:leaf\"]";
200 let data: Test = serde_json5::from_str(json).expect("deserialize");
201 assert_eq!(
202 data,
203 Test(vec![
204 selectors::parse_selector::<FastError>("core/foo:some/branch:leaf").unwrap(),
205 selectors::parse_selector::<FastError>("core/bar:root/twig:leaf").unwrap()
206 ])
207 );
208 }
209
210 #[fuchsia::test]
211 fn refuse_invalid_selectors() {
212 let not_string = "42";
213 let bad_list = "[ 42, \"core/bar:not:a:selector:root/twig:leaf\"]";
214 serde_json5::from_str::<Test>(not_string).expect_err("should fail");
215 serde_json5::from_str::<Test>(bad_list).expect_err("should fail");
216 }
217
218 #[fuchsia::test]
219 fn test_greater_than_zero() {
220 let data: TestInt = serde_json5::from_str("1").unwrap();
221 assert_eq!(data, TestInt(1));
222 serde_json5::from_str::<Test>("0").expect_err("0 is not greater than 0");
223 serde_json5::from_str::<Test>("-1").expect_err("-1 is not greater than 0");
224 }
225
226 #[fuchsia::test]
227 fn wild_card_selectors() {
228 let good_selector = r#"["bootstrap/*-drivers\\:*:[name=fvm]root:field"]"#;
229 assert_matches!(serde_json5::from_str::<Test>(good_selector), Ok(_));
230
231 let good_selector = r#"["core/bluetooth-core/bt-host-collection\\:bt-host_*:root:field"]"#;
232 assert_matches!(serde_json5::from_str::<Test>(good_selector), Ok(_));
233
234 let bad_selector = r#"["not_bootstrap/*-drivers\\:*:[name=fvm]root:field"]"#;
235 assert_matches!(serde_json5::from_str::<Test>(bad_selector), Err(_));
236
237 let not_exact_collection_match = r#"["bootstrap/*-drivers*:[name=fvm]root:field"]"#;
238 assert_matches!(serde_json5::from_str::<Test>(not_exact_collection_match), Err(_));
239
240 let missing_filter = r#"["not_bootstrap/*-drivers\\:*:root:field"]"#;
241 assert_matches!(serde_json5::from_str::<Test>(missing_filter), Err(_));
242 }
243}