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