component_debug/
doctor.rs
1use 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#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
22#[derive(Debug)]
23pub struct RouteReport {
24 pub decl_type: DeclType,
25
26 pub capability: String,
29
30 pub error_summary: Option<String>,
32
33 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
70pub 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
110pub 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
124fn 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 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}