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