component_debug/
doctor.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 anyhow::{format_err, Result};
6use fidl_fuchsia_sys2 as fsys;
7use moniker::Moniker;
8use prettytable::format::consts::FORMAT_CLEAN;
9use prettytable::{cell, row, Row, Table};
10
11const USE_TITLE: &'static str = "Used Capability";
12const EXPOSE_TITLE: &'static str = "Exposed Capability";
13const SUCCESS_SUMMARY: &'static str = "Success";
14const CAPABILITY_COLUMN_WIDTH: usize = 50;
15const SUMMARY_COLUMN_WIDTH: usize = 80;
16
17#[cfg(feature = "serde")]
18use serde::{Deserialize, Serialize};
19
20// Analytical information about a capability.
21#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
22#[derive(Debug)]
23pub struct RouteReport {
24    pub decl_type: DeclType,
25
26    /// The name of the capability (for DeclType::Expose), or the path of
27    /// the capability in the namespace (for DeclType::Use).
28    pub capability: String,
29
30    /// If Some, indicates a routing error for this route.
31    pub error_summary: Option<String>,
32
33    /// The requested level of availability of the capability.
34    pub availability: Option<cm_rust::Availability>,
35}
36
37impl TryFrom<fsys::RouteReport> for RouteReport {
38    type Error = anyhow::Error;
39
40    fn try_from(report: fsys::RouteReport) -> Result<Self> {
41        let decl_type =
42            report.decl_type.ok_or_else(|| format_err!("missing decl type"))?.try_into()?;
43        let capability = report.capability.ok_or_else(|| format_err!("missing capability name"))?;
44        let availability: Option<cm_rust::Availability> =
45            report.availability.map(cm_rust::Availability::from);
46        let error_summary = if let Some(error) = report.error { error.summary } else { None };
47        Ok(RouteReport { decl_type, capability, error_summary, availability })
48    }
49}
50
51#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
52#[derive(Debug, PartialEq)]
53pub enum DeclType {
54    Use,
55    Expose,
56}
57
58impl TryFrom<fsys::DeclType> for DeclType {
59    type Error = anyhow::Error;
60
61    fn try_from(value: fsys::DeclType) -> std::result::Result<Self, Self::Error> {
62        match value {
63            fsys::DeclType::Use => Ok(DeclType::Use),
64            fsys::DeclType::Expose => Ok(DeclType::Expose),
65            _ => Err(format_err!("unknown decl type")),
66        }
67    }
68}
69
70/// Returns a list of individual RouteReports for use and expose declarations
71/// for the component. Any individual report with `error_summary` set to Some()
72/// indicates a routing error.
73pub async fn validate_routes(
74    route_validator: &fsys::RouteValidatorProxy,
75    moniker: &Moniker,
76) -> Result<Vec<RouteReport>> {
77    let reports = match route_validator.validate(&moniker.to_string()).await? {
78        Ok(reports) => reports,
79        Err(e) => {
80            return Err(format_err!(
81                "Component manager returned an unexpected error during validation: {:?}\n\
82                 The state of the component instance may have changed.\n\
83                 Please report this to the Component Framework team.",
84                e
85            ));
86        }
87    };
88
89    reports.into_iter().map(|r| r.try_into()).collect()
90}
91
92fn format(report: &RouteReport) -> Row {
93    let capability = match report.availability {
94        Some(cm_rust::Availability::Required) | None => report.capability.clone(),
95        Some(availability) => format!("{} ({})", report.capability, availability),
96    };
97    let capability = textwrap::fill(&capability, CAPABILITY_COLUMN_WIDTH);
98    let (mark, summary) = if let Some(summary) = &report.error_summary {
99        let mark = ansi_term::Color::Red.paint("[✗]");
100        let summary = textwrap::fill(summary, SUMMARY_COLUMN_WIDTH);
101        (mark, summary)
102    } else {
103        let mark = ansi_term::Color::Green.paint("[✓]");
104        let summary = textwrap::fill(SUCCESS_SUMMARY, SUMMARY_COLUMN_WIDTH);
105        (mark, summary)
106    };
107    row!(mark, capability, summary)
108}
109
110// Construct the used and exposed capability tables from the given route reports.
111pub fn create_tables(reports: &Vec<RouteReport>) -> (Table, Table) {
112    let mut use_table = new_table(USE_TITLE);
113    let mut expose_table = new_table(EXPOSE_TITLE);
114
115    for report in reports {
116        match &report.decl_type {
117            DeclType::Use => use_table.add_row(format(&report)),
118            DeclType::Expose => expose_table.add_row(format(&report)),
119        };
120    }
121    (use_table, expose_table)
122}
123
124// Create a new table with the given title.
125fn new_table(title: &str) -> Table {
126    let mut table = Table::new();
127    table.set_format(*FORMAT_CLEAN);
128    table.set_titles(row!("", title.to_string(), "Result"));
129    table
130}
131
132#[cfg(test)]
133mod test {
134    use super::*;
135    use fidl::endpoints::create_proxy_and_stream;
136    use futures::TryStreamExt;
137
138    fn route_validator(
139        expected_moniker: &'static str,
140        reports: Vec<fsys::RouteReport>,
141    ) -> fsys::RouteValidatorProxy {
142        let (route_validator, mut stream) = create_proxy_and_stream::<fsys::RouteValidatorMarker>();
143        fuchsia_async::Task::local(async move {
144            match stream.try_next().await.unwrap().unwrap() {
145                fsys::RouteValidatorRequest::Validate { moniker, responder, .. } => {
146                    assert_eq!(Moniker::parse_str(expected_moniker), Moniker::parse_str(&moniker));
147                    responder.send(Ok(&reports)).unwrap();
148                }
149                fsys::RouteValidatorRequest::Route { .. } => {
150                    panic!("unexpected Route request");
151                }
152            }
153        })
154        .detach();
155        route_validator
156    }
157
158    #[fuchsia_async::run_singlethreaded(test)]
159    async fn test_errors() {
160        let validator = route_validator(
161            "/test",
162            vec![fsys::RouteReport {
163                capability: Some("fuchsia.foo.bar".to_string()),
164                decl_type: Some(fsys::DeclType::Use),
165                error: Some(fsys::RouteError {
166                    summary: Some("Access denied".to_string()),
167                    ..Default::default()
168                }),
169                ..Default::default()
170            }],
171        );
172
173        let mut reports =
174            validate_routes(&validator, &Moniker::parse_str("test").unwrap()).await.unwrap();
175        assert_eq!(reports.len(), 1);
176
177        let report = reports.remove(0);
178        assert_eq!(report.capability, "fuchsia.foo.bar");
179        assert_eq!(report.decl_type, DeclType::Use);
180
181        let error = report.error_summary.unwrap();
182        assert_eq!(error, "Access denied");
183    }
184
185    #[fuchsia_async::run_singlethreaded(test)]
186    async fn test_no_errors() {
187        let validator = route_validator(
188            "/test",
189            vec![fsys::RouteReport {
190                capability: Some("fuchsia.foo.bar".to_string()),
191                decl_type: Some(fsys::DeclType::Use),
192                error: None,
193                ..Default::default()
194            }],
195        );
196
197        let mut reports =
198            validate_routes(&validator, &Moniker::parse_str("test").unwrap()).await.unwrap();
199        assert_eq!(reports.len(), 1);
200
201        let report = reports.remove(0);
202        assert_eq!(report.capability, "fuchsia.foo.bar");
203        assert_eq!(report.decl_type, DeclType::Use);
204        assert!(report.error_summary.is_none());
205    }
206
207    #[fuchsia_async::run_singlethreaded(test)]
208    async fn test_no_routes() {
209        let validator = route_validator("test", vec![]);
210
211        let reports =
212            validate_routes(&validator, &Moniker::parse_str("test").unwrap()).await.unwrap();
213        assert!(reports.is_empty());
214    }
215
216    #[fuchsia_async::run_singlethreaded(test)]
217    async fn test_parse_error() {
218        let validator = route_validator(
219            "/test",
220            vec![
221                // Don't set any fields
222                fsys::RouteReport::default(),
223            ],
224        );
225
226        let result = validate_routes(&validator, &Moniker::parse_str("test").unwrap()).await;
227        assert!(result.is_err());
228    }
229}