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