1#[cfg(feature = "fdomain")]
6use component_debug_fdomain as component_debug;
7
8use crate::commands::types::*;
9use crate::commands::utils;
10use crate::types::Error;
11use argh::{ArgsInfo, FromArgs};
12use component_debug::realm::Instance;
13use diagnostics_data::InspectData;
14use flex_fuchsia_diagnostics::Selector;
15use flex_fuchsia_sys2 as fsys;
16use serde::Serialize;
17use std::cmp::Ordering;
18use std::collections::BTreeSet;
19use std::fmt;
20
21#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, schemars::JsonSchema)]
22pub struct MonikerWithUrl {
23 pub moniker: String,
24 pub component_url: String,
25}
26
27#[derive(Debug, Eq, PartialEq, Serialize, schemars::JsonSchema)]
28#[serde(untagged)]
29pub enum ListResultItem {
30 Moniker(String),
31 MonikerWithUrl(MonikerWithUrl),
32}
33
34impl ListResultItem {
35 pub fn into_moniker(self) -> String {
36 let moniker = match self {
37 Self::Moniker(moniker) => moniker,
38 Self::MonikerWithUrl(MonikerWithUrl { moniker, .. }) => moniker,
39 };
40 selectors::sanitize_moniker_for_selectors(&moniker)
41 }
42}
43
44impl Ord for ListResultItem {
45 fn cmp(&self, other: &Self) -> Ordering {
46 match (self, other) {
47 (ListResultItem::Moniker(moniker), ListResultItem::Moniker(other_moniker))
48 | (
49 ListResultItem::MonikerWithUrl(MonikerWithUrl { moniker, .. }),
50 ListResultItem::MonikerWithUrl(MonikerWithUrl { moniker: other_moniker, .. }),
51 ) => moniker.cmp(other_moniker),
52 _ => unreachable!("all lists must contain variants of the same type"),
53 }
54 }
55}
56
57impl PartialOrd for ListResultItem {
58 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
60 Some(self.cmp(other))
61 }
62}
63
64#[derive(Serialize, schemars::JsonSchema)]
65pub struct ListResult(Vec<ListResultItem>);
66
67impl fmt::Display for ListResult {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 for item in self.0.iter() {
70 match item {
71 ListResultItem::Moniker(moniker) => writeln!(f, "{moniker}")?,
72 ListResultItem::MonikerWithUrl(MonikerWithUrl { component_url, moniker }) => {
73 writeln!(f, "{moniker}:")?;
74 writeln!(f, " {component_url}")?;
75 }
76 }
77 }
78 Ok(())
79 }
80}
81
82fn components_from_inspect_data(
83 inspect_data: Vec<InspectData>,
84) -> impl Iterator<Item = ListResultItem> {
85 inspect_data.into_iter().map(|value| {
86 ListResultItem::MonikerWithUrl(MonikerWithUrl {
87 moniker: value.moniker.to_string(),
88 component_url: value.metadata.component_url.into(),
89 })
90 })
91}
92
93pub fn list_response_items(
94 with_url: bool,
95 components: impl Iterator<Item = ListResultItem>,
96) -> Vec<ListResultItem> {
97 components
98 .map(|result| {
99 if with_url {
100 result
101 } else {
102 match result {
103 ListResultItem::Moniker(_) => result,
104 ListResultItem::MonikerWithUrl(val) => ListResultItem::Moniker(val.moniker),
105 }
106 }
107 })
108 .collect::<BTreeSet<_>>()
110 .into_iter()
111 .collect::<Vec<_>>()
112}
113
114#[derive(Default, ArgsInfo, FromArgs, PartialEq, Debug)]
117#[argh(subcommand, name = "list")]
118pub struct ListCommand {
119 #[argh(option)]
120 pub component: Option<String>,
125
126 #[argh(switch)]
127 pub with_url: bool,
129
130 #[argh(option)]
131 pub accessor: Option<String>,
141}
142
143impl Command for ListCommand {
144 type Result = ListResult;
145
146 async fn execute<P: DiagnosticsProvider>(self, provider: &P) -> Result<Self::Result, Error> {
147 let mut selectors = if let Some(query) = self.component {
148 let instances = find_components(provider.realm_query(), &query).await?;
149 instances_to_root_selectors(instances)?
150 } else {
151 vec![]
152 };
153 utils::ensure_tree_field_is_set(&mut selectors, None)?;
154 let inspect = provider.snapshot(self.accessor.as_deref(), selectors).await?;
155 let components = components_from_inspect_data(inspect);
156 let results = list_response_items(self.with_url, components);
157 Ok(ListResult(results))
158 }
159}
160
161async fn find_components(
162 realm_proxy: &fsys::RealmQueryProxy,
163 query: &str,
164) -> Result<Vec<Instance>, Error> {
165 component_debug::query::get_instances_from_query(query, realm_proxy)
166 .await
167 .map_err(Error::FuzzyMatchRealmQuery)
168}
169
170fn instances_to_root_selectors(instances: Vec<Instance>) -> Result<Vec<Selector>, Error> {
171 instances
172 .into_iter()
173 .map(|instance| instance.moniker)
174 .map(|moniker| {
175 let selector_str = format!("{moniker}:root");
176 selectors::parse_verbose(&selector_str).map_err(Error::PartialSelectorHint)
177 })
178 .collect::<Result<Vec<Selector>, Error>>()
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184 use diagnostics_data::{InspectDataBuilder, InspectHandleName, Timestamp};
185
186 #[fuchsia::test]
187 fn components_from_inspect_data_uses_diagnostics_ready() {
188 let inspect_data = vec![
189 InspectDataBuilder::new(
190 "some_moniker".try_into().unwrap(),
191 "fake-url",
192 Timestamp::from_nanos(123456789800i64),
193 )
194 .with_name(InspectHandleName::filename("fake-file"))
195 .build(),
196 InspectDataBuilder::new(
197 "other_moniker".try_into().unwrap(),
198 "other-fake-url",
199 Timestamp::from_nanos(123456789900i64),
200 )
201 .with_name(InspectHandleName::filename("fake-file"))
202 .build(),
203 InspectDataBuilder::new(
204 "some_moniker".try_into().unwrap(),
205 "fake-url",
206 Timestamp::from_nanos(123456789910i64),
207 )
208 .with_name(InspectHandleName::filename("fake-file"))
209 .build(),
210 InspectDataBuilder::new(
211 "different_moniker".try_into().unwrap(),
212 "different-fake-url",
213 Timestamp::from_nanos(123456790990i64),
214 )
215 .with_name(InspectHandleName::filename("fake-file"))
216 .build(),
217 ];
218
219 let components = components_from_inspect_data(inspect_data).collect::<Vec<_>>();
220
221 assert_eq!(components.len(), 4);
222 }
223}