component_debug/cli/
explore.rs1use 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 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}