1use anyhow::{format_err, Result};
6use flex_fuchsia_sys2 as fsys;
7use moniker::{ExtendedMoniker, Moniker};
8use prettytable::format::consts::FORMAT_CLEAN;
9use prettytable::{cell, row, Table};
10use std::fmt;
11
12const SUCCESS_SUMMARY: &'static str = "Success";
13const VOID_SUMMARY: &'static str = "Routed from void";
14
15#[cfg(feature = "serde")]
16use serde::{Deserialize, Serialize};
17
18#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
20#[derive(Debug)]
21pub struct RouteReport {
22 pub decl_type: DeclType,
23 pub capability: String,
24 pub error_summary: Option<String>,
25 pub source_moniker: Option<String>,
26 pub service_instances: Option<Vec<ServiceInstance>>,
27 pub dictionary_entries: Option<Vec<DictionaryEntry>>,
28 pub outcome: RouteOutcome,
29}
30
31#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
32#[derive(Debug, PartialEq)]
33pub struct ServiceInstance {
34 pub instance_name: String,
35 pub child_name: String,
36 pub child_instance_name: String,
37}
38
39impl TryFrom<fsys::ServiceInstance> for ServiceInstance {
40 type Error = anyhow::Error;
41
42 fn try_from(value: fsys::ServiceInstance) -> std::result::Result<Self, Self::Error> {
43 Ok(Self {
44 instance_name: value
45 .instance_name
46 .ok_or_else(|| format_err!("missing instance_name"))?,
47 child_name: value.child_name.ok_or_else(|| format_err!("missing child_name"))?,
48 child_instance_name: value
49 .child_instance_name
50 .ok_or_else(|| format_err!("missing child_instance_name"))?,
51 })
52 }
53}
54
55#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
56#[derive(Debug, PartialEq)]
57pub struct DictionaryEntry {
58 pub name: String,
59}
60
61impl TryFrom<fsys::DictionaryEntry> for DictionaryEntry {
62 type Error = anyhow::Error;
63
64 fn try_from(value: fsys::DictionaryEntry) -> std::result::Result<Self, Self::Error> {
65 Ok(Self { name: value.name.ok_or_else(|| format_err!("missing name"))? })
66 }
67}
68
69impl TryFrom<fsys::RouteReport> for RouteReport {
70 type Error = anyhow::Error;
71
72 fn try_from(report: fsys::RouteReport) -> Result<Self> {
73 let decl_type =
74 report.decl_type.ok_or_else(|| format_err!("missing decl type"))?.try_into()?;
75 let capability = report.capability.ok_or_else(|| format_err!("missing capability name"))?;
76 let error_summary = if let Some(error) = report.error { error.summary } else { None };
77 let source_moniker = report.source_moniker;
78 let service_instances = report
79 .service_instances
80 .map(|s| s.into_iter().map(|s| s.try_into()).collect())
81 .transpose()?;
82 let dictionary_entries = report
83 .dictionary_entries
84 .map(|s| s.into_iter().map(|s| s.try_into()).collect())
85 .transpose()?;
86 let outcome = match report.outcome {
87 Some(o) => o.try_into()?,
88 None => {
89 if error_summary.is_some() {
92 RouteOutcome::Failed
93 } else {
94 RouteOutcome::Success
95 }
96 }
97 };
98 Ok(RouteReport {
99 decl_type,
100 capability,
101 error_summary,
102 source_moniker,
103 service_instances,
104 dictionary_entries,
105 outcome,
106 })
107 }
108}
109
110#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
111#[derive(Debug, PartialEq)]
112pub enum DeclType {
113 Use,
114 Expose,
115}
116
117impl fmt::Display for DeclType {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119 let s = match self {
120 DeclType::Use => "use",
121 DeclType::Expose => "expose",
122 };
123 write!(f, "{}", s)
124 }
125}
126
127impl TryFrom<fsys::DeclType> for DeclType {
128 type Error = anyhow::Error;
129
130 fn try_from(value: fsys::DeclType) -> std::result::Result<Self, Self::Error> {
131 match value {
132 fsys::DeclType::Use => Ok(DeclType::Use),
133 fsys::DeclType::Expose => Ok(DeclType::Expose),
134 _ => Err(format_err!("unknown decl type")),
135 }
136 }
137}
138
139#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
140#[derive(Debug, PartialEq)]
141pub enum RouteOutcome {
142 Success,
143 Void,
144 Failed,
145}
146
147impl TryFrom<fsys::RouteOutcome> for RouteOutcome {
148 type Error = anyhow::Error;
149
150 fn try_from(value: fsys::RouteOutcome) -> std::result::Result<Self, Self::Error> {
151 match value {
152 fsys::RouteOutcome::Success => Ok(RouteOutcome::Success),
153 fsys::RouteOutcome::Void => Ok(RouteOutcome::Void),
154 fsys::RouteOutcome::Failed => Ok(RouteOutcome::Failed),
155 _ => Err(format_err!("unknown route outcome")),
156 }
157 }
158}
159
160pub async fn route(
162 route_validator: &fsys::RouteValidatorProxy,
163 moniker: Moniker,
164 targets: Vec<fsys::RouteTarget>,
165) -> Result<Vec<RouteReport>> {
166 let reports = match route_validator.route(&moniker.to_string(), &targets).await? {
167 Ok(reports) => reports,
168 Err(e) => {
169 return Err(format_err!(
170 "Component manager returned an unexpected error during routing: {:?}\n\
171 The state of the component instance may have changed.\n\
172 Please report this to the Component Framework team.",
173 e
174 ));
175 }
176 };
177
178 reports.into_iter().map(|r| r.try_into()).collect()
179}
180
181pub fn create_table(reports: Vec<RouteReport>) -> Table {
183 let mut table = Table::new();
184 table.set_format(*FORMAT_CLEAN);
185
186 let mut first = true;
187 for report in reports {
188 if first {
189 first = false;
190 } else {
191 table.add_empty_row();
192 }
193 add_report(report, &mut table);
194 }
195 table
196}
197
198fn add_report(report: RouteReport, table: &mut Table) {
199 table
200 .add_row(row!(r->"Capability: ", &format!("{} ({})", report.capability, report.decl_type)));
201 let (mark, summary) = match report.outcome {
202 RouteOutcome::Success => {
203 let mark = ansi_term::Color::Green.paint("[✓]");
204 (mark, SUCCESS_SUMMARY)
205 }
206 RouteOutcome::Void => {
207 let mark = ansi_term::Color::Yellow.paint("[~]");
208 (mark, VOID_SUMMARY)
209 }
210 RouteOutcome::Failed => {
211 let mark = ansi_term::Color::Red.paint("[✗]");
212 let summary = report
213 .error_summary
214 .as_ref()
215 .map(|s| s.as_str())
216 .unwrap_or("Missing error summary. This is a bug.");
217 (mark, summary)
218 }
219 };
220 table.add_row(row!(r->"Result: ", &format!("{} {}", mark, summary)));
221 if let Some(source_moniker) = report.source_moniker {
222 let source_moniker = match ExtendedMoniker::parse_str(&source_moniker) {
223 Ok(m) => m.to_string(),
224 Err(e) => format!("<invalid moniker>: {}: {}", e, source_moniker),
225 };
226 table.add_row(row!(r->"Source: ", source_moniker));
227 }
228 if let Some(service_instances) = report.service_instances {
229 let mut service_table = Table::new();
230 let mut format = *FORMAT_CLEAN;
231 format.padding(0, 0);
232 service_table.set_format(format);
233 let mut first = true;
234 for service_instance in service_instances {
235 if first {
236 first = false;
237 } else {
238 service_table.add_empty_row();
239 }
240 service_table.add_row(row!(r->"Name: ", &service_instance.instance_name));
241 service_table.add_row(row!(r->"Source child: ", &service_instance.child_name));
242 service_table.add_row(row!(r->"Name in child: ",
243 &service_instance.child_instance_name));
244 }
245 table.add_row(row!(r->"Service instances: ", service_table));
246 }
247 if let Some(dictionary_entries) = report.dictionary_entries {
248 let mut dict_table = Table::new();
249 let mut format = *FORMAT_CLEAN;
250 format.padding(0, 0);
251 dict_table.set_format(format);
252 for e in dictionary_entries {
253 dict_table.add_row(row!(&e.name));
254 }
255 table.add_row(row!(r->"Contents: ", dict_table));
256 }
257}
258
259#[cfg(test)]
260mod test {
261 use super::*;
262 use assert_matches::assert_matches;
263 use fidl::endpoints;
264 use fuchsia_async as fasync;
265 use futures::TryStreamExt;
266
267 fn route_validator(
268 expected_moniker: &'static str,
269 expected_targets: Vec<fsys::RouteTarget>,
270 reports: Vec<fsys::RouteReport>,
271 ) -> fsys::RouteValidatorProxy {
272 let (route_validator, mut stream) =
273 endpoints::create_proxy_and_stream::<fsys::RouteValidatorMarker>();
274 fasync::Task::local(async move {
275 match stream.try_next().await.unwrap().unwrap() {
276 fsys::RouteValidatorRequest::Validate { .. } => {
277 panic!("unexpected Validate request");
278 }
279 fsys::RouteValidatorRequest::Route { moniker, targets, responder } => {
280 assert_eq!(
281 Moniker::parse_str(expected_moniker).unwrap(),
282 Moniker::parse_str(&moniker).unwrap()
283 );
284 assert_eq!(expected_targets, targets);
285 responder.send(Ok(&reports)).unwrap();
286 }
287 }
288 })
289 .detach();
290 route_validator
291 }
292
293 #[fuchsia::test]
294 async fn test_errors() {
295 let targets =
296 vec![fsys::RouteTarget { decl_type: fsys::DeclType::Use, name: "fuchsia.foo".into() }];
297 let validator = route_validator(
298 "/test",
299 targets.clone(),
300 vec![fsys::RouteReport {
301 capability: Some("fuchsia.foo.bar".into()),
302 decl_type: Some(fsys::DeclType::Use),
303 error: Some(fsys::RouteError {
304 summary: Some("Access denied".into()),
305 ..Default::default()
306 }),
307 outcome: None,
309 ..Default::default()
310 }],
311 );
312
313 let mut reports =
314 route(&validator, Moniker::parse_str("./test").unwrap(), targets).await.unwrap();
315 assert_eq!(reports.len(), 1);
316
317 let report = reports.remove(0);
318 assert_matches!(
319 report,
320 RouteReport {
321 capability,
322 decl_type: DeclType::Use,
323 error_summary: Some(s),
324 source_moniker: None,
325 service_instances: None,
326 dictionary_entries: None,
327 outcome: RouteOutcome::Failed,
328 } if capability == "fuchsia.foo.bar" && s == "Access denied"
329 );
330 }
331
332 #[fuchsia::test]
333 async fn test_no_errors() {
334 let targets =
335 vec![fsys::RouteTarget { decl_type: fsys::DeclType::Use, name: "fuchsia.foo".into() }];
336 let validator = route_validator(
337 "/test",
338 targets.clone(),
339 vec![
340 fsys::RouteReport {
341 capability: Some("fuchsia.foo.bar".into()),
342 decl_type: Some(fsys::DeclType::Use),
343 source_moniker: Some("<component manager>".into()),
344 error: None,
345 outcome: Some(fsys::RouteOutcome::Void),
346 ..Default::default()
347 },
348 fsys::RouteReport {
349 capability: Some("fuchsia.foo.baz".into()),
350 decl_type: Some(fsys::DeclType::Expose),
351 source_moniker: Some("/test/src".into()),
352 service_instances: Some(vec![
353 fsys::ServiceInstance {
354 instance_name: Some("1234abcd".into()),
355 child_name: Some("a".into()),
356 child_instance_name: Some("default".into()),
357 ..Default::default()
358 },
359 fsys::ServiceInstance {
360 instance_name: Some("abcd1234".into()),
361 child_name: Some("b".into()),
362 child_instance_name: Some("other".into()),
363 ..Default::default()
364 },
365 ]),
366 dictionary_entries: Some(vec![
367 fsys::DictionaryEntry { name: Some("k1".into()), ..Default::default() },
368 fsys::DictionaryEntry { name: Some("k2".into()), ..Default::default() },
369 ]),
370 error: None,
371 outcome: None,
373 ..Default::default()
374 },
375 ],
376 );
377
378 let mut reports =
379 route(&validator, Moniker::parse_str("./test").unwrap(), targets).await.unwrap();
380 assert_eq!(reports.len(), 2);
381
382 let report = reports.remove(0);
383 assert_matches!(
384 report,
385 RouteReport {
386 capability,
387 decl_type: DeclType::Use,
388 error_summary: None,
389 source_moniker: Some(m),
390 service_instances: None,
391 dictionary_entries: None,
392 outcome: RouteOutcome::Void,
393 } if capability == "fuchsia.foo.bar" && m == "<component manager>"
394 );
395
396 let report = reports.remove(0);
397 assert_matches!(
398 report,
399 RouteReport {
400 capability,
401 decl_type: DeclType::Expose,
402 error_summary: None,
403 source_moniker: Some(m),
404 service_instances: Some(s),
405 dictionary_entries: Some(d),
406 outcome: RouteOutcome::Success,
407 } if capability == "fuchsia.foo.baz" && m == "/test/src"
408 && s == vec![
409 ServiceInstance {
410 instance_name: "1234abcd".into(),
411 child_name: "a".into(),
412 child_instance_name: "default".into(),
413 },
414 ServiceInstance {
415 instance_name: "abcd1234".into(),
416 child_name: "b".into(),
417 child_instance_name: "other".into(),
418 },
419 ]
420 && d == vec![
421 DictionaryEntry {
422 name: "k1".into(),
423 },
424 DictionaryEntry {
425 name: "k2".into(),
426 },
427 ]
428 );
429 }
430
431 #[fuchsia::test]
432 async fn test_no_routes() {
433 let validator = route_validator("/test", vec![], vec![]);
434
435 let reports =
436 route(&validator, Moniker::parse_str("./test").unwrap(), vec![]).await.unwrap();
437 assert!(reports.is_empty());
438 }
439
440 #[fuchsia::test]
441 async fn test_parse_error() {
442 let validator = route_validator(
443 "/test",
444 vec![],
445 vec![
446 fsys::RouteReport::default(),
448 ],
449 );
450
451 let result = route(&validator, Moniker::parse_str("./test").unwrap(), vec![]).await;
452 assert_matches!(result, Err(_));
453 }
454}