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