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
5use crate::commands::types::*;
6use crate::commands::utils;
7use crate::types::Error;
8use argh::{ArgsInfo, FromArgs};
9use component_debug::realm::Instance;
10use diagnostics_data::InspectData;
11use fidl_fuchsia_diagnostics::Selector;
12use fidl_fuchsia_sys2 as fsys;
13use serde::{Serialize, Serializer};
14use std::cmp::Ordering;
15use std::collections::BTreeSet;
16use std::fmt;
17
18#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Serialize)]
19pub struct MonikerWithUrl {
20    pub moniker: String,
21    pub component_url: String,
22}
23
24#[derive(Debug, Eq, PartialEq)]
25pub enum ListResultItem {
26    Moniker(String),
27    MonikerWithUrl(MonikerWithUrl),
28}
29
30impl ListResultItem {
31    pub fn into_moniker(self) -> String {
32        let moniker = match self {
33            Self::Moniker(moniker) => moniker,
34            Self::MonikerWithUrl(MonikerWithUrl { moniker, .. }) => moniker,
35        };
36        selectors::sanitize_moniker_for_selectors(&moniker)
37    }
38}
39
40impl Ord for ListResultItem {
41    fn cmp(&self, other: &Self) -> Ordering {
42        match (self, other) {
43            (ListResultItem::Moniker(moniker), ListResultItem::Moniker(other_moniker))
44            | (
45                ListResultItem::MonikerWithUrl(MonikerWithUrl { moniker, .. }),
46                ListResultItem::MonikerWithUrl(MonikerWithUrl { moniker: other_moniker, .. }),
47            ) => moniker.cmp(other_moniker),
48            _ => unreachable!("all lists must contain variants of the same type"),
49        }
50    }
51}
52
53impl PartialOrd for ListResultItem {
54    // Compare based on the moniker only. To enable sorting using the moniker only.
55    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
56        Some(self.cmp(other))
57    }
58}
59
60impl Serialize for ListResultItem {
61    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
62        match self {
63            Self::Moniker(string) => serializer.serialize_str(string),
64            Self::MonikerWithUrl(data) => data.serialize(serializer),
65        }
66    }
67}
68
69#[derive(Serialize)]
70pub struct ListResult(Vec<ListResultItem>);
71
72impl fmt::Display for ListResult {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        for item in self.0.iter() {
75            match item {
76                ListResultItem::Moniker(moniker) => writeln!(f, "{moniker}")?,
77                ListResultItem::MonikerWithUrl(MonikerWithUrl { component_url, moniker }) => {
78                    writeln!(f, "{moniker}:")?;
79                    writeln!(f, "  {component_url}")?;
80                }
81            }
82        }
83        Ok(())
84    }
85}
86
87fn components_from_inspect_data(
88    inspect_data: Vec<InspectData>,
89) -> impl Iterator<Item = ListResultItem> {
90    inspect_data.into_iter().map(|value| {
91        ListResultItem::MonikerWithUrl(MonikerWithUrl {
92            moniker: value.moniker.to_string(),
93            component_url: value.metadata.component_url.into(),
94        })
95    })
96}
97
98pub fn list_response_items(
99    with_url: bool,
100    components: impl Iterator<Item = ListResultItem>,
101) -> Vec<ListResultItem> {
102    components
103        .map(|result| {
104            if with_url {
105                result
106            } else {
107                match result {
108                    ListResultItem::Moniker(_) => result,
109                    ListResultItem::MonikerWithUrl(val) => ListResultItem::Moniker(val.moniker),
110                }
111            }
112        })
113        // Collect as btreeset to sort and remove potential duplicates.
114        .collect::<BTreeSet<_>>()
115        .into_iter()
116        .collect::<Vec<_>>()
117}
118
119/// Lists all components (relative to the scope where the archivist receives events from) of
120/// components that expose inspect.
121#[derive(Default, ArgsInfo, FromArgs, PartialEq, Debug)]
122#[argh(subcommand, name = "list")]
123pub struct ListCommand {
124    #[argh(option)]
125    /// a fuzzy-search query. May include URL, moniker, or manifest fragments.
126    /// a fauzzy-search query for the component we are interested in. May include URL, moniker, or
127    /// manifest fragments. If this is provided, the output will only contain monikers for
128    /// components that matched the query.
129    pub component: Option<String>,
130
131    #[argh(switch)]
132    /// also print the URL of the component.
133    pub with_url: bool,
134
135    #[argh(option)]
136    /// A selector specifying what `fuchsia.diagnostics.ArchiveAccessor` to connect to.
137    /// The selector will be in the form of:
138    /// <moniker>:fuchsia.diagnostics.ArchiveAccessor(.pipeline_name)?
139    ///
140    /// Typically this is the output of `iquery list-accessors`.
141    ///
142    /// For example: `bootstrap/archivist:fuchsia.diagnostics.ArchiveAccessor.feedback`
143    /// means that the command will connect to the `ArchiveAccecssor` filtered by the feedback
144    /// pipeline exposed by `bootstrap/archivist`.
145    pub accessor: Option<String>,
146}
147
148impl Command for ListCommand {
149    type Result = ListResult;
150
151    async fn execute<P: DiagnosticsProvider>(self, provider: &P) -> Result<Self::Result, Error> {
152        let mut selectors = if let Some(query) = self.component {
153            let instances = find_components(provider.realm_query(), &query).await?;
154            instances_to_root_selectors(instances)?
155        } else {
156            vec![]
157        };
158        utils::ensure_tree_field_is_set(&mut selectors, None)?;
159        let inspect = provider.snapshot(self.accessor.as_deref(), selectors.into_iter()).await?;
160        let components = components_from_inspect_data(inspect);
161        let results = list_response_items(self.with_url, components);
162        Ok(ListResult(results))
163    }
164}
165
166async fn find_components(
167    realm_proxy: &fsys::RealmQueryProxy,
168    query: &str,
169) -> Result<Vec<Instance>, Error> {
170    component_debug::query::get_instances_from_query(query, realm_proxy)
171        .await
172        .map_err(Error::FuzzyMatchRealmQuery)
173}
174
175fn instances_to_root_selectors(instances: Vec<Instance>) -> Result<Vec<Selector>, Error> {
176    instances
177        .into_iter()
178        .map(|instance| instance.moniker)
179        .map(|moniker| {
180            let selector_str = format!("{moniker}:root");
181            selectors::parse_verbose(&selector_str).map_err(Error::PartialSelectorHint)
182        })
183        .collect::<Result<Vec<Selector>, Error>>()
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189    use diagnostics_data::{InspectDataBuilder, InspectHandleName, Timestamp};
190
191    #[fuchsia::test]
192    fn components_from_inspect_data_uses_diagnostics_ready() {
193        let inspect_data = vec![
194            InspectDataBuilder::new(
195                "some_moniker".try_into().unwrap(),
196                "fake-url",
197                Timestamp::from_nanos(123456789800i64),
198            )
199            .with_name(InspectHandleName::filename("fake-file"))
200            .build(),
201            InspectDataBuilder::new(
202                "other_moniker".try_into().unwrap(),
203                "other-fake-url",
204                Timestamp::from_nanos(123456789900i64),
205            )
206            .with_name(InspectHandleName::filename("fake-file"))
207            .build(),
208            InspectDataBuilder::new(
209                "some_moniker".try_into().unwrap(),
210                "fake-url",
211                Timestamp::from_nanos(123456789910i64),
212            )
213            .with_name(InspectHandleName::filename("fake-file"))
214            .build(),
215            InspectDataBuilder::new(
216                "different_moniker".try_into().unwrap(),
217                "different-fake-url",
218                Timestamp::from_nanos(123456790990i64),
219            )
220            .with_name(InspectHandleName::filename("fake-file"))
221            .build(),
222        ];
223
224        let components = components_from_inspect_data(inspect_data).collect::<Vec<_>>();
225
226        assert_eq!(components.len(), 4);
227    }
228}