component_debug/cli/
collection.rs

1// Copyright 2023 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::realm::{get_all_instances, get_resolved_declaration, Durability};
6use anyhow::{bail, Result};
7use cm_rust::{OfferDeclCommon, OfferTarget};
8use fidl_fuchsia_sys2 as fsys;
9use moniker::Moniker;
10use prettytable::format::consts::FORMAT_CLEAN;
11use prettytable::{cell, row, Table};
12
13struct Collection {
14    name: String,
15    moniker: Moniker,
16    durability: Durability,
17    environment: Option<String>,
18    offered_capabilities: Vec<String>,
19}
20
21pub async fn collection_list_cmd<W: std::io::Write>(
22    realm_query: fsys::RealmQueryProxy,
23    mut writer: W,
24) -> Result<()> {
25    let collections = get_all_collections(&realm_query).await?;
26
27    let table = create_table(collections);
28    table.print(&mut writer)?;
29
30    Ok(())
31}
32
33pub async fn collection_show_cmd<W: std::io::Write>(
34    query: String,
35    realm_query: fsys::RealmQueryProxy,
36    mut writer: W,
37) -> Result<()> {
38    let collections = get_all_collections(&realm_query).await?;
39
40    let filtered_collections: Vec<Collection> =
41        collections.into_iter().filter(|c| c.name.contains(&query)).collect();
42
43    if filtered_collections.is_empty() {
44        bail!("No collections found for query \"{}\"", query);
45    }
46
47    for collection in filtered_collections {
48        let table = create_verbose_table(&collection);
49        table.print(&mut writer)?;
50        writeln!(writer, "")?;
51    }
52
53    Ok(())
54}
55
56async fn get_all_collections(realm_query: &fsys::RealmQueryProxy) -> Result<Vec<Collection>> {
57    let instances = get_all_instances(realm_query).await?;
58    let mut collections = vec![];
59
60    for instance in instances {
61        if instance.resolved_info.is_some() {
62            let mut instance_collections =
63                get_all_collections_of_instance(&instance.moniker, realm_query).await?;
64            collections.append(&mut instance_collections);
65        }
66    }
67
68    Ok(collections)
69}
70
71async fn get_all_collections_of_instance(
72    moniker: &Moniker,
73    realm_query: &fsys::RealmQueryProxy,
74) -> Result<Vec<Collection>> {
75    let manifest = get_resolved_declaration(moniker, realm_query).await?;
76    let mut collections = vec![];
77
78    for collection in manifest.collections {
79        let mut offered_capabilities = vec![];
80
81        for offer in &manifest.offers {
82            match offer.target() {
83                OfferTarget::Collection(name) => {
84                    if name == &collection.name {
85                        offered_capabilities.push(offer.target_name().to_string());
86                    }
87                }
88                _ => {}
89            }
90        }
91
92        collections.push(Collection {
93            name: collection.name.to_string(),
94            moniker: moniker.clone(),
95            durability: collection.durability.into(),
96            environment: collection.environment.map(|e| e.to_string()),
97            offered_capabilities,
98        });
99    }
100
101    Ok(collections)
102}
103
104fn create_table(collections: Vec<Collection>) -> Table {
105    let mut table = Table::new();
106    table.set_format(*FORMAT_CLEAN);
107    table.set_titles(row!("Moniker", "Name", "Durability", "Environment"));
108
109    for collection in collections {
110        let environment = collection.environment.unwrap_or_else(|| "N/A".to_string());
111        table.add_row(row!(
112            collection.moniker.to_string(),
113            collection.name,
114            collection.durability.to_string(),
115            environment
116        ));
117    }
118
119    table
120}
121
122fn create_verbose_table(collection: &Collection) -> Table {
123    let mut table = Table::new();
124    table.set_format(*FORMAT_CLEAN);
125    table.add_row(row!(r->"Moniker:", collection.moniker.to_string()));
126    table.add_row(row!(r->"Collection Name:", collection.name));
127    table.add_row(row!(r->"Durability:", collection.durability.to_string()));
128
129    let environment = collection.environment.clone().unwrap_or_else(|| "N/A".to_string());
130    table.add_row(row!(r->"Environment:", environment));
131
132    let offered_capabilities = collection.offered_capabilities.join("\n");
133    table.add_row(row!(r->"Offered Capabilities:", offered_capabilities));
134
135    table
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141    use crate::test_utils::*;
142    use fidl_fuchsia_component_decl as fdecl;
143    use std::collections::HashMap;
144
145    fn create_query() -> fsys::RealmQueryProxy {
146        // Serve RealmQuery for CML components.
147        let query = serve_realm_query(
148            vec![fsys::Instance {
149                moniker: Some("./my_foo".to_string()),
150                url: Some("fuchsia-pkg://fuchsia.com/foo#meta/foo.cm".to_string()),
151                instance_id: Some("1234567890".to_string()),
152                resolved_info: Some(fsys::ResolvedInfo {
153                    resolved_url: Some("fuchsia-pkg://fuchsia.com/foo#meta/foo.cm".to_string()),
154                    execution_info: None,
155                    ..Default::default()
156                }),
157                ..Default::default()
158            }],
159            HashMap::from([(
160                "./my_foo".to_string(),
161                fdecl::Component {
162                    offers: Some(vec![fdecl::Offer::Protocol(fdecl::OfferProtocol {
163                        source: Some(fdecl::Ref::Parent(fdecl::ParentRef)),
164                        source_name: Some("fuchsia.foo.bar".to_string()),
165                        target: Some(fdecl::Ref::Collection(fdecl::CollectionRef {
166                            name: "coll1".to_string(),
167                        })),
168                        target_name: Some("fuchsia.foo.bar".to_string()),
169                        dependency_type: Some(fdecl::DependencyType::Strong),
170                        availability: Some(fdecl::Availability::Required),
171                        ..Default::default()
172                    })]),
173                    collections: Some(vec![fdecl::Collection {
174                        name: Some("coll1".to_string()),
175                        durability: Some(fdecl::Durability::Transient),
176                        ..Default::default()
177                    }]),
178                    ..Default::default()
179                },
180            )]),
181            HashMap::from([]),
182            HashMap::from([]),
183        );
184        query
185    }
186
187    #[fuchsia::test]
188    async fn get_all_collections_test() {
189        let query = create_query();
190
191        let mut collections = get_all_collections(&query).await.unwrap();
192
193        assert_eq!(collections.len(), 1);
194
195        let collection = collections.remove(0);
196
197        assert_eq!(collection.name, "coll1");
198        assert_eq!(collection.moniker, Moniker::parse_str("/my_foo").unwrap());
199        assert_eq!(collection.durability, Durability::Transient);
200        assert!(collection.environment.is_none());
201        assert_eq!(collection.offered_capabilities, vec!["fuchsia.foo.bar"]);
202    }
203}