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