component_debug/
query.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::realm::{get_all_instances, Instance};
6use anyhow::{bail, Result};
7use moniker::Moniker;
8
9use flex_fuchsia_sys2 as fsys;
10
11/// Retrieves a list of CML instances that match a given string query.
12///
13/// The string query can be a partial match on the following properties:
14/// * component moniker
15/// * component URL
16/// * component instance ID
17pub async fn get_instances_from_query(
18    query: &str,
19    realm_query: &fsys::RealmQueryProxy,
20) -> Result<Vec<Instance>> {
21    let instances = get_all_instances(realm_query).await?;
22    let query_moniker = Moniker::parse_str(&query).ok();
23
24    // Try and find instances that contain the query in any of the identifiers
25    // (moniker, URL, instance ID).
26    let mut filtered_instances: Vec<Instance> = instances
27        .into_iter()
28        .filter(|i| {
29            let url_match = i.url.contains(&query);
30            let moniker_match = i.moniker.to_string().contains(&query);
31            let normalized_query_moniker_match =
32                matches!(&query_moniker, Some(m) if i.moniker.to_string().contains(&m.to_string()));
33            let id_match = i.instance_id.as_ref().map_or(false, |id| id.contains(&query));
34            url_match || moniker_match || normalized_query_moniker_match || id_match
35        })
36        .collect();
37
38    // For stability sort the list by moniker.
39    filtered_instances.sort_by_key(|i| i.moniker.to_string());
40
41    // If the query is an exact-match of any of the results, return that
42    // result only.
43    if let Some(m) = query_moniker {
44        if let Some(matched) = filtered_instances.iter().find(|i| i.moniker == m) {
45            return Ok(vec![matched.clone()]);
46        }
47    }
48
49    Ok(filtered_instances)
50}
51
52/// Retrieves exactly one instance matching a given string query.
53///
54/// The string query can be a partial match on the following properties:
55/// * component moniker
56/// * component URL
57/// * component instance ID
58///
59/// If more than one instance matches the query, an error is thrown.
60/// If no instance matches the query, an error is thrown.
61pub async fn get_single_instance_from_query(
62    query: &str,
63    realm_query: &fsys::RealmQueryProxy,
64) -> Result<Instance> {
65    // Get all instance monikers that match the query and ensure there is only one.
66    let mut instances = get_instances_from_query(&query, &realm_query).await?;
67    if instances.len() > 1 {
68        let monikers: Vec<String> = instances.into_iter().map(|i| i.moniker.to_string()).collect();
69        let monikers = monikers.join("\n");
70        bail!("The query {:?} matches more than one component instance:\n{}\n\nTo avoid ambiguity, use one of the above monikers instead.", query, monikers);
71    }
72    if instances.is_empty() {
73        bail!("No matching component instance found for query {:?}.", query);
74    }
75    let instance = instances.remove(0);
76    Ok(instance)
77}
78
79/// Retrieves a list of CML instance monikers that will match a given string query.
80///
81/// The string query can be a partial match on the following properties:
82/// * component moniker
83/// * component URL
84/// * component instance ID
85pub async fn get_cml_monikers_from_query(
86    query: &str,
87    realm_query: &fsys::RealmQueryProxy,
88) -> Result<Vec<Moniker>> {
89    // Special-case the root moniker since it will substring match every moniker
90    // below.
91    let query_moniker = Moniker::parse_str(&query).ok();
92    if let Some(m) = &query_moniker {
93        if m.is_root() {
94            return Ok(vec![m.clone()]);
95        }
96    }
97
98    let instances = get_instances_from_query(query, realm_query).await?;
99    let monikers: Vec<Moniker> = instances.into_iter().map(|i| i.moniker).collect();
100
101    // If the query is an exact-match of any of the results, return that
102    // result only.
103    if let Some(m) = query_moniker {
104        if monikers.contains(&m) {
105            return Ok(vec![m]);
106        }
107    }
108
109    Ok(monikers)
110}
111
112/// Retrieves exactly one CML instance moniker that will match a given string query.
113///
114/// The string query can be a partial match on the following properties:
115/// * component moniker
116/// * component URL
117/// * component instance ID
118///
119/// If more than one instance matches the query, an error is thrown.
120/// If no instance matches the query, an error is thrown.
121pub async fn get_cml_moniker_from_query(
122    query: &str,
123    realm_query: &fsys::RealmQueryProxy,
124) -> Result<Moniker> {
125    // Get all instance monikers that match the query and ensure there is only one.
126    let mut monikers = get_cml_monikers_from_query(&query, &realm_query).await?;
127    if monikers.len() > 1 {
128        let monikers: Vec<String> = monikers.into_iter().map(|m| m.to_string()).collect();
129        let monikers = monikers.join("\n");
130        bail!("The query {:?} matches more than one component instance:\n{}\n\nTo avoid ambiguity, use one of the above monikers instead.", query, monikers);
131    }
132    if monikers.is_empty() {
133        bail!("No matching component instance found for query {:?}.", query);
134    }
135    let moniker = monikers.remove(0);
136    Ok(moniker)
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142    use crate::test_utils::serve_realm_query_instances;
143
144    fn setup_fake_realm_query() -> fsys::RealmQueryProxy {
145        setup_fake_realm_query_with_entries(vec![
146            ("/core/foo", "#meta/1bar.cm", "123456"),
147            ("/core/boo", "#meta/2bar.cm", "456789"),
148        ])
149    }
150
151    fn setup_fake_realm_query_with_entries(
152        entries: Vec<(&str, &str, &str)>,
153    ) -> fsys::RealmQueryProxy {
154        let instances = entries
155            .iter()
156            .map(|(moniker, url, instance_id)| fsys::Instance {
157                moniker: Some(moniker.to_string()),
158                url: Some(url.to_string()),
159                instance_id: Some(instance_id.to_string()),
160                resolved_info: None,
161                ..Default::default()
162            })
163            .collect::<Vec<_>>();
164        serve_realm_query_instances(instances)
165    }
166
167    #[fuchsia_async::run_singlethreaded(test)]
168    async fn test_get_cml_monikers_from_query_exact_match_and_prefixes() {
169        let realm_query = setup_fake_realm_query_with_entries(vec![
170            ("/", "#meta/1.cm", "1"),
171            ("/core", "#meta/2.cm", "2"),
172            ("/core:one", "#meta/3.cm", "3"),
173            ("/core:one/child", "#meta/4.cm", "4"),
174        ]);
175
176        assert_eq!(
177            get_cml_monikers_from_query("/", &realm_query).await.unwrap(),
178            vec![Moniker::parse_str("/").unwrap()]
179        );
180
181        assert_eq!(
182            get_cml_monikers_from_query("/core", &realm_query).await.unwrap(),
183            vec![Moniker::parse_str("/core").unwrap()]
184        );
185
186        assert_eq!(
187            get_cml_monikers_from_query("/core:one", &realm_query).await.unwrap(),
188            vec![Moniker::parse_str("/core:one").unwrap()]
189        );
190
191        assert_eq!(
192            get_cml_monikers_from_query("/core:o", &realm_query).await.unwrap(),
193            vec![
194                Moniker::parse_str("/core:one").unwrap(),
195                Moniker::parse_str("/core:one/child").unwrap(),
196            ]
197        );
198    }
199
200    #[fuchsia_async::run_singlethreaded(test)]
201    async fn test_get_cml_monikers_from_query_moniker_more_than_1() {
202        let realm_query = setup_fake_realm_query();
203        let results = get_cml_monikers_from_query("core", &realm_query).await.unwrap();
204        assert_eq!(
205            results,
206            vec![
207                Moniker::parse_str("/core/boo").unwrap(),
208                Moniker::parse_str("/core/foo").unwrap()
209            ]
210        );
211    }
212
213    #[fuchsia_async::run_singlethreaded(test)]
214    async fn test_get_cml_monikers_from_query_moniker_exactly_1() {
215        let realm_query = setup_fake_realm_query();
216        let results = get_cml_monikers_from_query("foo", &realm_query).await.unwrap();
217        assert_eq!(results, vec![Moniker::parse_str("/core/foo").unwrap()]);
218    }
219
220    #[fuchsia_async::run_singlethreaded(test)]
221    async fn test_get_cml_monikers_from_query_url_more_than_1() {
222        let realm_query = setup_fake_realm_query();
223        let results = get_cml_monikers_from_query("bar.cm", &realm_query).await.unwrap();
224        assert_eq!(
225            results,
226            vec![
227                Moniker::parse_str("/core/boo").unwrap(),
228                Moniker::parse_str("/core/foo").unwrap()
229            ]
230        );
231    }
232
233    #[fuchsia_async::run_singlethreaded(test)]
234    async fn test_get_cml_monikers_from_query_url_exactly_1() {
235        let realm_query = setup_fake_realm_query();
236        let results = get_cml_monikers_from_query("2bar.cm", &realm_query).await.unwrap();
237        assert_eq!(results, vec![Moniker::parse_str("/core/boo").unwrap()]);
238    }
239
240    #[fuchsia_async::run_singlethreaded(test)]
241    async fn test_get_cml_monikers_from_query_id_more_than_1() {
242        let realm_query = setup_fake_realm_query();
243        let results = get_cml_monikers_from_query("456", &realm_query).await.unwrap();
244        assert_eq!(
245            results,
246            vec![
247                Moniker::parse_str("/core/boo").unwrap(),
248                Moniker::parse_str("/core/foo").unwrap()
249            ]
250        );
251    }
252
253    #[fuchsia_async::run_singlethreaded(test)]
254    async fn test_get_cml_monikers_from_query_id_exactly_1() {
255        let realm_query = setup_fake_realm_query();
256        let results = get_cml_monikers_from_query("123", &realm_query).await.unwrap();
257        assert_eq!(results, vec![Moniker::parse_str("/core/foo").unwrap()]);
258    }
259
260    #[fuchsia_async::run_singlethreaded(test)]
261    async fn test_get_cml_monikers_from_query_no_results() {
262        let realm_query = setup_fake_realm_query();
263        let results = get_cml_monikers_from_query("qwerty", &realm_query).await.unwrap();
264        assert_eq!(results.len(), 0);
265    }
266
267    #[fuchsia_async::run_singlethreaded(test)]
268    async fn test_get_cml_moniker_from_query_no_match() {
269        let realm_query = setup_fake_realm_query();
270        get_cml_moniker_from_query("qwerty", &realm_query).await.unwrap_err();
271    }
272
273    #[fuchsia_async::run_singlethreaded(test)]
274    async fn test_get_cml_moniker_from_query_multiple_match() {
275        let realm_query = setup_fake_realm_query();
276        get_cml_moniker_from_query("bar.cm", &realm_query).await.unwrap_err();
277    }
278
279    #[fuchsia_async::run_singlethreaded(test)]
280    async fn test_get_cml_moniker_from_query_moniker_single_match() {
281        let realm_query = setup_fake_realm_query();
282        let moniker = get_cml_moniker_from_query("foo", &realm_query).await.unwrap();
283        assert_eq!(moniker, Moniker::parse_str("/core/foo").unwrap());
284
285        let realm_query = setup_fake_realm_query();
286        let moniker = get_cml_moniker_from_query("/core/foo", &realm_query).await.unwrap();
287        assert_eq!(moniker, Moniker::parse_str("/core/foo").unwrap());
288    }
289
290    #[fuchsia_async::run_singlethreaded(test)]
291    async fn test_get_cml_moniker_from_url_moniker_single_match() {
292        let realm_query = setup_fake_realm_query();
293        let moniker = get_cml_moniker_from_query("2bar.cm", &realm_query).await.unwrap();
294        assert_eq!(moniker, Moniker::parse_str("/core/boo").unwrap());
295    }
296
297    #[fuchsia_async::run_singlethreaded(test)]
298    async fn test_get_cml_moniker_from_url_id_single_match() {
299        let realm_query = setup_fake_realm_query();
300        let moniker = get_cml_moniker_from_query("123", &realm_query).await.unwrap();
301        assert_eq!(moniker, Moniker::parse_str("/core/foo").unwrap());
302    }
303}