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