Skip to main content

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::{Instance, get_all_instances};
6use anyhow::{Result, bail};
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!(
71            "The query {:?} matches more than one component instance:\n{}\n\nTo avoid ambiguity, use one of the above monikers instead.",
72            query,
73            monikers
74        );
75    }
76    if instances.is_empty() {
77        bail!("No matching component instance found for query {:?}.", query);
78    }
79    let instance = instances.remove(0);
80    Ok(instance)
81}
82
83/// Retrieves a list of CML instance monikers that will match a given string query.
84///
85/// The string query can be a partial match on the following properties:
86/// * component moniker
87/// * component URL
88/// * component instance ID
89pub async fn get_cml_monikers_from_query(
90    query: &str,
91    realm_query: &fsys::RealmQueryProxy,
92) -> Result<Vec<Moniker>> {
93    // Special-case the root moniker since it will substring match every moniker
94    // below.
95    let query_moniker = Moniker::parse_str(&query).ok();
96    if let Some(m) = &query_moniker {
97        if m.is_root() {
98            return Ok(vec![m.clone()]);
99        }
100    }
101
102    let instances = get_instances_from_query(query, realm_query).await?;
103    let monikers: Vec<Moniker> = instances.into_iter().map(|i| i.moniker).collect();
104
105    // If the query is an exact-match of any of the results, return that
106    // result only.
107    if let Some(m) = query_moniker {
108        if monikers.contains(&m) {
109            return Ok(vec![m]);
110        }
111    }
112
113    Ok(monikers)
114}
115
116/// Retrieves exactly one CML instance moniker that will match a given string query.
117///
118/// The string query can be a partial match on the following properties:
119/// * component moniker
120/// * component URL
121/// * component instance ID
122///
123/// If more than one instance matches the query, an error is thrown.
124/// If no instance matches the query, an error is thrown.
125pub async fn get_cml_moniker_from_query(
126    query: &str,
127    realm_query: &fsys::RealmQueryProxy,
128) -> Result<Moniker> {
129    // Get all instance monikers that match the query and ensure there is only one.
130    let mut monikers = get_cml_monikers_from_query(&query, &realm_query).await?;
131    if monikers.len() > 1 {
132        let monikers: Vec<String> = monikers.into_iter().map(|m| m.to_string()).collect();
133        let monikers = monikers.join("\n");
134        bail!(
135            "The query {:?} matches more than one component instance:\n{}\n\nTo avoid ambiguity, use one of the above monikers instead.",
136            query,
137            monikers
138        );
139    }
140    if monikers.is_empty() {
141        bail!("No matching component instance found for query {:?}.", query);
142    }
143    let moniker = monikers.remove(0);
144    Ok(moniker)
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150    use crate::test_utils::serve_realm_query_instances;
151
152    fn setup_fake_realm_query() -> fsys::RealmQueryProxy {
153        setup_fake_realm_query_with_entries(vec![
154            ("/core/foo", "#meta/1bar.cm", "123456"),
155            ("/core/boo", "#meta/2bar.cm", "456789"),
156        ])
157    }
158
159    fn setup_fake_realm_query_with_entries(
160        entries: Vec<(&str, &str, &str)>,
161    ) -> fsys::RealmQueryProxy {
162        let instances = entries
163            .iter()
164            .map(|(moniker, url, instance_id)| fsys::Instance {
165                moniker: Some(moniker.to_string()),
166                url: Some(url.to_string()),
167                instance_id: Some(instance_id.to_string()),
168                resolved_info: None,
169                ..Default::default()
170            })
171            .collect::<Vec<_>>();
172        serve_realm_query_instances(instances)
173    }
174
175    #[fuchsia::test]
176    async fn test_get_cml_monikers_from_query_exact_match_and_prefixes() {
177        let realm_query = setup_fake_realm_query_with_entries(vec![
178            ("/", "#meta/1.cm", "1"),
179            ("/core", "#meta/2.cm", "2"),
180            ("/core:one", "#meta/3.cm", "3"),
181            ("/core:one/child", "#meta/4.cm", "4"),
182        ]);
183
184        assert_eq!(
185            get_cml_monikers_from_query("/", &realm_query).await.unwrap(),
186            vec![Moniker::parse_str("/").unwrap()]
187        );
188
189        assert_eq!(
190            get_cml_monikers_from_query("/core", &realm_query).await.unwrap(),
191            vec![Moniker::parse_str("/core").unwrap()]
192        );
193
194        assert_eq!(
195            get_cml_monikers_from_query("/core:one", &realm_query).await.unwrap(),
196            vec![Moniker::parse_str("/core:one").unwrap()]
197        );
198
199        assert_eq!(
200            get_cml_monikers_from_query("/core:o", &realm_query).await.unwrap(),
201            vec![
202                Moniker::parse_str("/core:one").unwrap(),
203                Moniker::parse_str("/core:one/child").unwrap(),
204            ]
205        );
206    }
207
208    #[fuchsia::test]
209    async fn test_get_cml_monikers_from_query_moniker_more_than_1() {
210        let realm_query = setup_fake_realm_query();
211        let results = get_cml_monikers_from_query("core", &realm_query).await.unwrap();
212        assert_eq!(
213            results,
214            vec![
215                Moniker::parse_str("/core/boo").unwrap(),
216                Moniker::parse_str("/core/foo").unwrap()
217            ]
218        );
219    }
220
221    #[fuchsia::test]
222    async fn test_get_cml_monikers_from_query_moniker_exactly_1() {
223        let realm_query = setup_fake_realm_query();
224        let results = get_cml_monikers_from_query("foo", &realm_query).await.unwrap();
225        assert_eq!(results, vec![Moniker::parse_str("/core/foo").unwrap()]);
226    }
227
228    #[fuchsia::test]
229    async fn test_get_cml_monikers_from_query_url_more_than_1() {
230        let realm_query = setup_fake_realm_query();
231        let results = get_cml_monikers_from_query("bar.cm", &realm_query).await.unwrap();
232        assert_eq!(
233            results,
234            vec![
235                Moniker::parse_str("/core/boo").unwrap(),
236                Moniker::parse_str("/core/foo").unwrap()
237            ]
238        );
239    }
240
241    #[fuchsia::test]
242    async fn test_get_cml_monikers_from_query_url_exactly_1() {
243        let realm_query = setup_fake_realm_query();
244        let results = get_cml_monikers_from_query("2bar.cm", &realm_query).await.unwrap();
245        assert_eq!(results, vec![Moniker::parse_str("/core/boo").unwrap()]);
246    }
247
248    #[fuchsia::test]
249    async fn test_get_cml_monikers_from_query_id_more_than_1() {
250        let realm_query = setup_fake_realm_query();
251        let results = get_cml_monikers_from_query("456", &realm_query).await.unwrap();
252        assert_eq!(
253            results,
254            vec![
255                Moniker::parse_str("/core/boo").unwrap(),
256                Moniker::parse_str("/core/foo").unwrap()
257            ]
258        );
259    }
260
261    #[fuchsia::test]
262    async fn test_get_cml_monikers_from_query_id_exactly_1() {
263        let realm_query = setup_fake_realm_query();
264        let results = get_cml_monikers_from_query("123", &realm_query).await.unwrap();
265        assert_eq!(results, vec![Moniker::parse_str("/core/foo").unwrap()]);
266    }
267
268    #[fuchsia::test]
269    async fn test_get_cml_monikers_from_query_no_results() {
270        let realm_query = setup_fake_realm_query();
271        let results = get_cml_monikers_from_query("qwerty", &realm_query).await.unwrap();
272        assert_eq!(results.len(), 0);
273    }
274
275    #[fuchsia::test]
276    async fn test_get_cml_moniker_from_query_no_match() {
277        let realm_query = setup_fake_realm_query();
278        get_cml_moniker_from_query("qwerty", &realm_query).await.unwrap_err();
279    }
280
281    #[fuchsia::test]
282    async fn test_get_cml_moniker_from_query_multiple_match() {
283        let realm_query = setup_fake_realm_query();
284        get_cml_moniker_from_query("bar.cm", &realm_query).await.unwrap_err();
285    }
286
287    #[fuchsia::test]
288    async fn test_get_cml_moniker_from_query_moniker_single_match() {
289        let realm_query = setup_fake_realm_query();
290        let moniker = get_cml_moniker_from_query("foo", &realm_query).await.unwrap();
291        assert_eq!(moniker, Moniker::parse_str("/core/foo").unwrap());
292
293        let realm_query = setup_fake_realm_query();
294        let moniker = get_cml_moniker_from_query("/core/foo", &realm_query).await.unwrap();
295        assert_eq!(moniker, Moniker::parse_str("/core/foo").unwrap());
296    }
297
298    #[fuchsia::test]
299    async fn test_get_cml_moniker_from_url_moniker_single_match() {
300        let realm_query = setup_fake_realm_query();
301        let moniker = get_cml_moniker_from_query("2bar.cm", &realm_query).await.unwrap();
302        assert_eq!(moniker, Moniker::parse_str("/core/boo").unwrap());
303    }
304
305    #[fuchsia::test]
306    async fn test_get_cml_moniker_from_url_id_single_match() {
307        let realm_query = setup_fake_realm_query();
308        let moniker = get_cml_moniker_from_query("123", &realm_query).await.unwrap();
309        assert_eq!(moniker, Moniker::parse_str("/core/foo").unwrap());
310    }
311}