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