component_debug/cli/
explore.rs

1// Copyright 2023 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::explore::*;
6use crate::query::get_cml_moniker_from_query;
7use anyhow::{Result, anyhow};
8use flex_client::ProxyHasDomain;
9use {flex_fuchsia_dash as fdash, flex_fuchsia_data as fdata, flex_fuchsia_sys2 as fsys};
10
11pub async fn explore_cmd(
12    query: String,
13    ns_layout: DashNamespaceLayout,
14    command: Option<String>,
15    overridden_tools_urls: Vec<String>,
16    dash_launcher: fdash::LauncherProxy,
17    realm_query: fsys::RealmQueryProxy,
18    stdout: socket_to_stdio::Stdout<'_>,
19) -> Result<()> {
20    let moniker = get_cml_moniker_from_query(&query, &realm_query).await?;
21    println!("Moniker: {}", moniker);
22
23    let tool_urls = if overridden_tools_urls.len() != 0 {
24        overridden_tools_urls
25    } else {
26        // We haven't overridden the tool_urls on the command line, so check the component manifest
27        // to see if it has default tool_urls
28        let urls = get_tool_urls_from_component_manifest(&realm_query, &moniker)
29            .await
30            .unwrap_or_else(|e| {
31                println!("Error loading tool urls from component manifest: {e:?}");
32                vec![]
33            });
34        println!("Using tool URLs from component manifest: {urls:?}");
35        urls
36    };
37
38    let (client, server) = realm_query.domain().create_stream_socket();
39
40    explore_over_socket(moniker, server, tool_urls, command, ns_layout, &dash_launcher).await?;
41
42    #[cfg(not(feature = "fdomain"))]
43    #[allow(clippy::large_futures)]
44    socket_to_stdio::connect_socket_to_stdio(client, stdout).await?;
45
46    #[cfg(feature = "fdomain")]
47    #[allow(clippy::large_futures)]
48    socket_to_stdio::connect_fdomain_socket_to_stdio(client, stdout).await?;
49
50    let exit_code = wait_for_shell_exit(&dash_launcher).await?;
51
52    std::process::exit(exit_code);
53}
54
55async fn get_tool_urls_from_component_manifest(
56    query: &fsys::RealmQueryProxy,
57    moniker: &moniker::Moniker,
58) -> Result<Vec<String>> {
59    let component_manifest = crate::realm::get_resolved_declaration(moniker, query)
60        .await
61        .map_err(|e| anyhow!("Couldn't get manifest for component {moniker}: {e:?}"))?;
62    let Some(facets) = component_manifest.facets else {
63        return Ok(vec![]);
64    };
65
66    urls_from_facets(facets)
67}
68
69fn urls_from_facets(facets: fdata::Dictionary) -> Result<Vec<String>> {
70    let mut urls = vec![];
71    for facet in facets.entries.as_ref().unwrap_or(&vec![]) {
72        if !facet.key.eq("fuchsia.dash.launcher-tool-urls") {
73            continue;
74        }
75        let Some(val) = facet.value.clone() else {
76            continue;
77        };
78
79        match *val {
80            fdata::DictionaryValue::Str(tool) => {
81                urls.push(tool);
82            }
83            fdata::DictionaryValue::StrVec(tools) => {
84                urls.extend(tools);
85            }
86            _ => {
87                return Err(anyhow!(
88                    "no parsable value for tool_urls facet. Override by passing --tools: {facet:?}"
89                ));
90            }
91        }
92    }
93    Ok(urls)
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99    use assert_matches::assert_matches;
100    use flex_fuchsia_data as fdata;
101
102    #[test]
103    fn test_urls_from_facets_empty() {
104        let facets = fdata::Dictionary { entries: Some(vec![]), ..Default::default() };
105        let result = urls_from_facets(facets).unwrap();
106        assert!(result.is_empty());
107    }
108
109    #[test]
110    fn test_urls_from_facets_no_matching_key() {
111        let facets = fdata::Dictionary {
112            entries: Some(vec![fdata::DictionaryEntry {
113                key: "other.key".to_string(),
114                value: Some(Box::new(fdata::DictionaryValue::Str("value".to_string()))),
115            }]),
116            ..Default::default()
117        };
118        let result = urls_from_facets(facets).unwrap();
119        assert!(result.is_empty());
120    }
121
122    #[test]
123    fn test_urls_from_facets_single_url() {
124        let facets = fdata::Dictionary {
125            entries: Some(vec![fdata::DictionaryEntry {
126                key: "fuchsia.dash.launcher-tool-urls".to_string(),
127                value: Some(Box::new(fdata::DictionaryValue::Str("url1".to_string()))),
128            }]),
129            ..Default::default()
130        };
131        let result = urls_from_facets(facets).unwrap();
132        assert_eq!(result, vec!["url1".to_string()]);
133    }
134
135    #[test]
136    fn test_urls_from_facets_multiple_urls() {
137        let facets = fdata::Dictionary {
138            entries: Some(vec![fdata::DictionaryEntry {
139                key: "fuchsia.dash.launcher-tool-urls".to_string(),
140                value: Some(Box::new(fdata::DictionaryValue::StrVec(vec![
141                    "url1".to_string(),
142                    "url2".to_string(),
143                ]))),
144            }]),
145            ..Default::default()
146        };
147        let result = urls_from_facets(facets).unwrap();
148        assert_eq!(result, vec!["url1".to_string(), "url2".to_string()]);
149    }
150
151    #[test]
152    fn test_urls_from_facets_invalid_value_type() {
153        let facets = fdata::Dictionary {
154            entries: Some(vec![fdata::DictionaryEntry {
155                key: "fuchsia.dash.launcher-tool-urls".to_string(),
156                value: Some(Box::new(fdata::DictionaryValue::ObjVec(vec![]))),
157            }]),
158            ..Default::default()
159        };
160        let result = urls_from_facets(facets);
161        assert_matches!(result, Err(_));
162    }
163
164    #[test]
165    fn test_urls_from_facets_mixed_entries() {
166        let facets = fdata::Dictionary {
167            entries: Some(vec![
168                fdata::DictionaryEntry {
169                    key: "other.key".to_string(),
170                    value: Some(Box::new(fdata::DictionaryValue::Str("value".to_string()))),
171                },
172                fdata::DictionaryEntry {
173                    key: "fuchsia.dash.launcher-tool-urls".to_string(),
174                    value: Some(Box::new(fdata::DictionaryValue::Str("url1".to_string()))),
175                },
176            ]),
177            ..Default::default()
178        };
179        let result = urls_from_facets(facets).unwrap();
180        assert_eq!(result, vec!["url1".to_string()]);
181    }
182
183    #[test]
184    fn test_urls_from_facets_none_value() {
185        let facets = fdata::Dictionary {
186            entries: Some(vec![fdata::DictionaryEntry {
187                key: "fuchsia.dash.launcher-tool-urls".to_string(),
188                value: None,
189            }]),
190            ..Default::default()
191        };
192        let result = urls_from_facets(facets).unwrap();
193        assert!(result.is_empty());
194    }
195}