Skip to main content

iquery/commands/
list.rs

1// Copyright 2020 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
5#[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    // Compare based on the moniker only. To enable sorting using the moniker only.
59    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 as btreeset to sort and remove potential duplicates.
109        .collect::<BTreeSet<_>>()
110        .into_iter()
111        .collect::<Vec<_>>()
112}
113
114/// Lists all components (relative to the scope where the archivist receives events from) of
115/// components that expose inspect.
116#[derive(Default, ArgsInfo, FromArgs, PartialEq, Debug)]
117#[argh(subcommand, name = "list")]
118pub struct ListCommand {
119    #[argh(option)]
120    /// a fuzzy-search query. May include URL, moniker, or manifest fragments.
121    /// a fauzzy-search query for the component we are interested in. May include URL, moniker, or
122    /// manifest fragments. If this is provided, the output will only contain monikers for
123    /// components that matched the query.
124    pub component: Option<String>,
125
126    #[argh(switch)]
127    /// also print the URL of the component.
128    pub with_url: bool,
129
130    #[argh(option)]
131    /// a selector specifying what `fuchsia.diagnostics.ArchiveAccessor` to connect to.
132    /// The selector will be in the form of:
133    /// <moniker>:fuchsia.diagnostics.ArchiveAccessor(.pipeline_name)?
134    ///
135    /// Typically this is the output of `iquery list-accessors`.
136    ///
137    /// For example: `bootstrap/archivist:fuchsia.diagnostics.ArchiveAccessor.feedback`
138    /// means that the command will connect to the `ArchiveAccecssor` filtered by the feedback
139    /// pipeline exposed by `bootstrap/archivist`.
140    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}