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