Skip to main content

routing/bedrock/
with_porcelain.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::WeakInstanceTokenExt;
6use crate::component_instance::{ComponentInstanceInterface, WeakExtendedInstanceInterface};
7use crate::error::{ErrorReporter, RouteRequestErrorInfo, RoutingError};
8use crate::rights::Rights;
9use crate::subdir::SubDir;
10use async_trait::async_trait;
11use capability_source::CapabilitySource;
12use cm_rust::{CapabilityTypeName, EventScope, FidlIntoNative, NativeIntoFidl};
13use cm_types::Availability;
14use fidl_fuchsia_component_runtime::RouteRequest;
15use fidl_fuchsia_component_sandbox as fsandbox;
16use fidl_fuchsia_io as fio;
17use moniker::{ExtendedMoniker, Moniker};
18use router_error::RouterError;
19use runtime_capabilities::{
20    Capability, CapabilityBound, Dictionary, Routable, Router, WeakInstanceToken,
21};
22use std::collections::HashMap;
23use std::sync::{Arc, LazyLock};
24use strum::IntoEnumIterator;
25
26#[cfg(target_os = "fuchsia")]
27use fuchsia_trace as trace;
28
29struct PorcelainRouter<T: CapabilityBound, R, C: ComponentInstanceInterface, const D: bool> {
30    router: Arc<Router<T>>,
31    porcelain_type: CapabilityTypeName,
32    availability: Availability,
33    rights: Option<Rights>,
34    subdir: Option<SubDir>,
35    inherit_rights: Option<bool>,
36    event_stream_scope: Option<(Moniker, Box<[EventScope]>)>,
37    target: WeakExtendedInstanceInterface<C>,
38    route_request: RouteRequestErrorInfo,
39    error_reporter: R,
40    should_log: bool,
41    #[allow(dead_code)]
42    tracing: bool,
43}
44
45#[async_trait]
46impl<T: CapabilityBound, R: ErrorReporter, C: ComponentInstanceInterface + 'static, const D: bool>
47    Routable<T> for PorcelainRouter<T, R, C, D>
48{
49    async fn route(
50        &self,
51        request: RouteRequest,
52        target: Arc<WeakInstanceToken>,
53    ) -> Result<Option<Arc<T>>, RouterError> {
54        #[allow(unused)]
55        let moniker: Option<ExtendedMoniker> = self
56            .tracing
57            .then(|| <Arc<WeakInstanceToken> as WeakInstanceTokenExt<C>>::moniker(&target));
58
59        #[cfg(target_os = "fuchsia")]
60        if self.tracing {
61            trace::duration_begin!("component_manager", "route_capability",
62                                   "target" => moniker.as_ref().unwrap().as_str(),
63                                   "type" => self.route_request.type_name().as_ref(),
64                                   "capability" => self.route_request.name().as_ref());
65        }
66
67        let result = match self.route_inner(request, D, target).await {
68            Err(err) if self.should_log => {
69                self.error_reporter
70                    .report(&self.route_request, &err, self.target.clone().into())
71                    .await;
72                Err(err)
73            }
74            other_result => other_result,
75        };
76
77        #[cfg(target_os = "fuchsia")]
78        if self.tracing {
79            trace::duration_end!("component_manager", "route_capability",
80                                 "target" => moniker.as_ref().unwrap().as_str(),
81                                 "type" => self.route_request.type_name().as_ref(),
82                                 "capability" => self.route_request.name().as_ref());
83        }
84
85        result
86    }
87
88    async fn route_debug(
89        &self,
90        request: RouteRequest,
91        target: Arc<WeakInstanceToken>,
92    ) -> Result<CapabilitySource, RouterError> {
93        #[allow(unused)]
94        let moniker: Option<ExtendedMoniker> = self
95            .tracing
96            .then(|| <Arc<WeakInstanceToken> as WeakInstanceTokenExt<C>>::moniker(&target));
97
98        #[cfg(target_os = "fuchsia")]
99        if self.tracing {
100            trace::duration_begin!("component_manager", "route_capability_debug",
101                                   "target" => moniker.as_ref().unwrap().as_str(),
102                                   "type" => self.route_request.type_name().as_ref(),
103                                   "capability" => self.route_request.name().as_ref());
104        }
105
106        let result = match self.route_debug_inner(request, D, target).await {
107            Err(err) if self.should_log => {
108                self.error_reporter
109                    .report(&self.route_request, &err, self.target.clone().into())
110                    .await;
111                Err(err)
112            }
113            other_result => other_result,
114        };
115
116        #[cfg(target_os = "fuchsia")]
117        if self.tracing {
118            trace::duration_end!("component_manager", "route_capability_debug",
119                                 "target" => moniker.as_ref().unwrap().as_str(),
120                                 "type" => self.route_request.type_name().as_ref(),
121                                 "capability" => self.route_request.name().as_ref());
122        }
123
124        result
125    }
126}
127
128impl<T: CapabilityBound, R: ErrorReporter, C: ComponentInstanceInterface + 'static, const D: bool>
129    PorcelainRouter<T, R, C, D>
130{
131    async fn route_inner(
132        &self,
133        request: RouteRequest,
134        supply_default: bool,
135        target: Arc<WeakInstanceToken>,
136    ) -> Result<Option<Arc<T>>, RouterError> {
137        let request = self.check_and_compute_request(request, supply_default)?;
138        self.router.route(request, target).await
139    }
140
141    async fn route_debug_inner(
142        &self,
143        request: RouteRequest,
144        supply_default: bool,
145        target: Arc<WeakInstanceToken>,
146    ) -> Result<CapabilitySource, RouterError> {
147        let request = self.check_and_compute_request(request, supply_default)?;
148        self.router.route_debug(request, target).await
149    }
150
151    fn check_and_compute_request(
152        &self,
153        request: RouteRequest,
154        supply_default: bool,
155    ) -> Result<RouteRequest, RouterError> {
156        let PorcelainRouter {
157            router: _,
158            porcelain_type,
159            availability,
160            rights,
161            subdir,
162            inherit_rights,
163            event_stream_scope,
164            target,
165            route_request: _,
166            error_reporter: _,
167            should_log: _,
168            tracing: _,
169        } = self;
170        let mut request = if request != RouteRequest::default() {
171            request
172        } else {
173            if !supply_default {
174                Err(RouterError::InvalidArgs)?;
175            }
176            let mut request = RouteRequest::default();
177            request.build_type_name = Some(porcelain_type.to_string());
178            request.availability = Some(availability.native_into_fidl());
179            if let Some(rights) = rights {
180                request.directory_rights = Some(fio::Flags::from(*rights));
181            }
182            if let Some(inherit_rights) = inherit_rights {
183                request.inherit_rights = Some(*inherit_rights);
184            }
185            if let Some((scope_moniker, scope)) = event_stream_scope.as_ref() {
186                request.event_stream_scope_moniker = Some(scope_moniker.to_string());
187                request.event_stream_scope = Some(scope.clone().native_into_fidl());
188            }
189            request
190        };
191
192        let moniker: ExtendedMoniker = match target {
193            WeakExtendedInstanceInterface::Component(t) => t.moniker.clone().into(),
194            WeakExtendedInstanceInterface::AboveRoot(_) => ExtendedMoniker::ComponentManager,
195        };
196        check_porcelain_type(&moniker, &request, *porcelain_type)?;
197        let updated_availability = check_availability(&moniker, &request, *availability)?;
198
199        check_and_compute_rights(&moniker, &mut request, &rights)?;
200        if let Some(new_subdir) = check_and_compute_subdir(&moniker, &request, &subdir)? {
201            request.sub_directory_path = Some(new_subdir.as_ref().clone().native_into_fidl());
202        }
203        if let Some((new_scope_moniker, new_scope)) = event_stream_scope.as_ref() {
204            // If the scope is already set then it's a smaller scope (because we can't expose
205            // these), so only set our scope if the request doesn't have one yet.
206            if request.event_stream_scope_moniker.is_none() {
207                request.event_stream_scope_moniker =
208                    Some(new_scope_moniker.clone().native_into_fidl());
209                request.event_stream_scope = Some(new_scope.clone().native_into_fidl());
210            }
211        }
212
213        // Everything checks out, forward the request.
214        request.availability = Some(updated_availability.native_into_fidl());
215        Ok(request)
216    }
217}
218
219fn check_porcelain_type(
220    moniker: &ExtendedMoniker,
221    request: &RouteRequest,
222    expected_type: CapabilityTypeName,
223) -> Result<(), RouterError> {
224    let capability_type: CapabilityTypeName = request
225        .build_type_name
226        .as_ref()
227        .ok_or_else(|| RoutingError::BedrockMissingCapabilityType {
228            type_name: expected_type.to_string(),
229            moniker: moniker.clone(),
230        })?
231        .parse()
232        .map_err(|_| RouterError::InvalidArgs)?;
233    if capability_type != expected_type {
234        Err(RoutingError::BedrockWrongCapabilityType {
235            moniker: moniker.clone(),
236            actual: capability_type.to_string(),
237            expected: expected_type.to_string(),
238        })?;
239    }
240    Ok(())
241}
242
243fn check_availability(
244    moniker: &ExtendedMoniker,
245    request: &RouteRequest,
246    availability: Availability,
247) -> Result<Availability, RouterError> {
248    // The availability of the request must be compatible with the
249    // availability of this step of the route.
250    let request_availability =
251        request.availability.ok_or(fsandbox::RouterError::InvalidArgs).inspect_err(|e| {
252            log::error!("request {:?} did not have availability metadata: {e:?}", request)
253        })?;
254    crate::availability::advance(&moniker, request_availability.fidl_into_native(), availability)
255        .map_err(|e| RoutingError::from(e).into())
256}
257
258fn check_and_compute_rights(
259    moniker: &ExtendedMoniker,
260    request: &mut RouteRequest,
261    rights: &Option<Rights>,
262) -> Result<(), RouterError> {
263    let Some(rights) = rights else {
264        return Ok(());
265    };
266    let inherit = request.inherit_rights.ok_or(RouterError::InvalidArgs)?;
267    let request_rights: Rights = match request.directory_rights {
268        Some(request_rights) => request_rights.into(),
269        None => {
270            if inherit {
271                request.directory_rights = Some(fio::Flags::from(*rights));
272                *rights
273            } else {
274                Err(RouterError::InvalidArgs)?
275            }
276        }
277    };
278    // The rights of the previous step (if any) of the route must be
279    // compatible with this step of the route.
280    if let Some(intermediate_rights) = request.directory_intermediate_rights {
281        Rights::from(intermediate_rights)
282            .validate_next(&rights, moniker.clone().into())
283            .map_err(|e| router_error::RouterError::from(RoutingError::from(e)))?;
284    };
285    request.directory_intermediate_rights = Some(fio::Flags::from(*rights));
286    // The rights of the request must be compatible with the
287    // rights of this step of the route.
288    request_rights.validate_next(&rights, moniker.clone().into()).map_err(RoutingError::from)?;
289    Ok(())
290}
291
292fn check_and_compute_subdir(
293    moniker: &ExtendedMoniker,
294    request: &RouteRequest,
295    subdir: &Option<SubDir>,
296) -> Result<Option<SubDir>, RouterError> {
297    let Some(mut subdir_from_decl) = subdir.clone() else {
298        return Ok(None);
299    };
300
301    let request_subdir: Option<SubDir> =
302        request.sub_directory_path.as_ref().map(|s| SubDir::new(s).expect("invalid sub directory"));
303
304    if let Some(request_subdir) = request_subdir {
305        let success = subdir_from_decl.as_mut().extend(request_subdir.clone().into());
306        if !success {
307            return Err(RoutingError::PathTooLong {
308                moniker: moniker.clone(),
309                path: subdir_from_decl.to_string(),
310                keyword: request_subdir.to_string(),
311            }
312            .into());
313        }
314    }
315    Ok(Some(subdir_from_decl))
316}
317
318pub type DefaultMetadataFn = Arc<dyn Fn(Availability) -> Dictionary + Send + Sync + 'static>;
319
320/// Builds a router that ensures the capability request has an availability strength that is at
321/// least the provided `availability`. A default `RouteRequest` is populated with `metadata_fn` if
322/// the client passes an empty `RouteRequest`.
323pub struct PorcelainBuilder<
324    T: CapabilityBound,
325    R: ErrorReporter,
326    C: ComponentInstanceInterface + 'static,
327    const D: bool,
328> {
329    router: Arc<Router<T>>,
330    porcelain_type: CapabilityTypeName,
331    availability: Option<Availability>,
332    rights: Option<Rights>,
333    subdir: Option<SubDir>,
334    inherit_rights: Option<bool>,
335    event_stream_scope: Option<(Moniker, Box<[EventScope]>)>,
336    target: Option<WeakExtendedInstanceInterface<C>>,
337    error_info: Option<RouteRequestErrorInfo>,
338    error_reporter: Option<R>,
339    should_log: bool,
340    #[allow(dead_code)]
341    tracing: bool,
342}
343
344impl<T: CapabilityBound, R: ErrorReporter, C: ComponentInstanceInterface + 'static, const D: bool>
345    PorcelainBuilder<T, R, C, D>
346{
347    fn new(router: Arc<Router<T>>, porcelain_type: CapabilityTypeName) -> Self {
348        Self {
349            router,
350            porcelain_type,
351            availability: None,
352            rights: None,
353            subdir: None,
354            inherit_rights: None,
355            event_stream_scope: None,
356            target: None,
357            error_info: None,
358            error_reporter: None,
359            should_log: false,
360            tracing: false,
361        }
362    }
363
364    pub fn log_errors(mut self) -> Self {
365        self.should_log = true;
366        self
367    }
368
369    pub fn with_tracing(mut self) -> Self {
370        self.tracing = true;
371        self
372    }
373
374    /// The [Availability] attribute for this route.
375    /// REQUIRED.
376    pub fn availability(mut self, a: Availability) -> Self {
377        self.availability = Some(a);
378        self
379    }
380
381    pub fn rights(mut self, rights: Option<Rights>) -> Self {
382        self.rights = rights;
383        self
384    }
385
386    pub fn subdir(mut self, subdir: SubDir) -> Self {
387        self.subdir = Some(subdir);
388        self
389    }
390
391    pub fn inherit_rights(mut self, inherit_rights: bool) -> Self {
392        self.inherit_rights = Some(inherit_rights);
393        self
394    }
395
396    pub fn event_stream_scope(mut self, scope: (Moniker, Box<[EventScope]>)) -> Self {
397        self.event_stream_scope = Some(scope);
398        self
399    }
400
401    /// The identity of the component on behalf of whom this routing request is performed, if the
402    /// caller passes a `None` request.
403    /// Either this or `target_above_root` is REQUIRED.
404    pub fn target(mut self, t: &Arc<C>) -> Self {
405        self.target = Some(WeakExtendedInstanceInterface::Component(t.as_weak()));
406        self
407    }
408
409    /// The identity of the "above root" instance that is component manager itself.
410    /// Either this or `target` is REQUIRED.
411    pub fn target_above_root(mut self, t: &Arc<C::TopInstance>) -> Self {
412        self.target = Some(WeakExtendedInstanceInterface::AboveRoot(Arc::downgrade(t)));
413        self
414    }
415
416    /// Object used to generate diagnostic information about the route that is logged if the route
417    /// fails. This is usually a [cm_rust] type that is castable to [RouteRequestErrorInfo]
418    /// REQUIRED.
419    pub fn error_info<S>(mut self, r: S) -> Self
420    where
421        RouteRequestErrorInfo: From<S>,
422    {
423        self.error_info = Some(RouteRequestErrorInfo::from(r));
424        self
425    }
426
427    /// The [ErrorReporter] used to log errors if routing fails.
428    /// REQUIRED.
429    pub fn error_reporter(mut self, r: R) -> Self {
430        self.error_reporter = Some(r);
431        self
432    }
433
434    /// Build the [PorcelainRouter] with attributes configured by this builder.
435    pub fn build(self) -> Arc<Router<T>> {
436        Router::new(PorcelainRouter::<T, R, C, D> {
437            router: self.router,
438            porcelain_type: self.porcelain_type,
439            availability: self.availability.expect("must set availability"),
440            rights: self.rights,
441            subdir: self.subdir,
442            inherit_rights: self.inherit_rights,
443            event_stream_scope: self.event_stream_scope,
444            target: self.target.expect("must set target"),
445            route_request: self.error_info.expect("must set route_request"),
446            error_reporter: self.error_reporter.expect("must set error_reporter"),
447            should_log: self.should_log,
448            tracing: self.tracing,
449        })
450    }
451}
452
453impl<R: ErrorReporter, T: CapabilityBound, C: ComponentInstanceInterface + 'static, const D: bool>
454    From<PorcelainBuilder<T, R, C, D>> for Capability
455where
456    Arc<Router<T>>: Into<Capability>,
457{
458    fn from(b: PorcelainBuilder<T, R, C, D>) -> Self {
459        b.build().into()
460    }
461}
462
463/// See [WithPorcelain::with_porcelain] for documentation.
464pub trait WithPorcelain<
465    T: CapabilityBound,
466    R: ErrorReporter,
467    C: ComponentInstanceInterface + 'static,
468>
469{
470    /// Returns a [PorcelainBuilder] you use to construct a new router with porcelain properties
471    /// that augments the `self`. See [PorcelainBuilder] for documentation of the supported
472    /// properties.
473    ///
474    /// If a `None` request is passed into the built router, the router will supply a default
475    /// request based on the values passed to the builder.
476    fn with_porcelain_with_default(
477        self,
478        type_: CapabilityTypeName,
479    ) -> PorcelainBuilder<T, R, C, true>;
480
481    /// Returns a [PorcelainBuilder] you use to construct a new router with porcelain properties
482    /// that augments the `self`. See [PorcelainBuilder] for documentation of the supported
483    /// properties.
484    ///
485    /// If a `None` request is passed into the built router, the router will throw an `InvalidArgs`
486    /// error.
487    fn with_porcelain_no_default(
488        self,
489        type_: CapabilityTypeName,
490    ) -> PorcelainBuilder<T, R, C, false>;
491}
492
493impl<T: CapabilityBound, R: ErrorReporter, C: ComponentInstanceInterface + 'static>
494    WithPorcelain<T, R, C> for Arc<Router<T>>
495{
496    fn with_porcelain_with_default(
497        self,
498        type_: CapabilityTypeName,
499    ) -> PorcelainBuilder<T, R, C, true> {
500        PorcelainBuilder::<T, R, C, true>::new(self, type_)
501    }
502
503    fn with_porcelain_no_default(
504        self,
505        type_: CapabilityTypeName,
506    ) -> PorcelainBuilder<T, R, C, false> {
507        PorcelainBuilder::<T, R, C, false>::new(self, type_)
508    }
509}
510
511pub fn metadata_for_porcelain_type(
512    typename: CapabilityTypeName,
513) -> Arc<dyn Fn(Availability) -> RouteRequest + Send + Sync + 'static> {
514    type MetadataMap = HashMap<
515        CapabilityTypeName,
516        Arc<dyn Fn(Availability) -> RouteRequest + Send + Sync + 'static>,
517    >;
518    static CLOSURES: LazyLock<MetadataMap> = LazyLock::new(|| {
519        fn entry_for_typename(
520            typename: CapabilityTypeName,
521        ) -> (CapabilityTypeName, Arc<dyn Fn(Availability) -> RouteRequest + Send + Sync + 'static>)
522        {
523            let v = Arc::new(move |availability: Availability| RouteRequest {
524                build_type_name: Some(typename.to_string()),
525                availability: Some(availability.native_into_fidl()),
526                ..Default::default()
527            });
528            (typename, v)
529        }
530        CapabilityTypeName::iter().map(entry_for_typename).collect()
531    });
532    CLOSURES.get(&typename).unwrap().clone()
533}
534
535#[cfg(test)]
536mod tests {
537    use super::*;
538    use crate::ResolvedInstanceInterface;
539    use crate::bedrock::sandbox_construction::ComponentSandbox;
540    use crate::component_instance::{ExtendedInstanceInterface, TopInstanceInterface};
541    use crate::error::ComponentInstanceError;
542    use crate::policy::GlobalPolicyChecker;
543    use assert_matches::assert_matches;
544    use capability_source::{BuiltinCapabilities, NamespaceCapabilities};
545    use cm_rust_testing::UseBuilder;
546    use cm_types::Url;
547    use fuchsia_sync::Mutex;
548    use moniker::Moniker;
549    use router_error::RouterError;
550    use runtime_capabilities::Data;
551    use std::sync::Arc;
552
553    #[derive(Debug)]
554    struct FakeComponent {
555        moniker: Moniker,
556    }
557
558    #[derive(Debug)]
559    struct FakeTopInstance {
560        ns: NamespaceCapabilities,
561        builtin: BuiltinCapabilities,
562    }
563
564    impl TopInstanceInterface for FakeTopInstance {
565        fn namespace_capabilities(&self) -> &NamespaceCapabilities {
566            &self.ns
567        }
568        fn builtin_capabilities(&self) -> &BuiltinCapabilities {
569            &self.builtin
570        }
571    }
572
573    #[async_trait]
574    impl ComponentInstanceInterface for FakeComponent {
575        type TopInstance = FakeTopInstance;
576
577        fn moniker(&self) -> &Moniker {
578            &self.moniker
579        }
580
581        fn url(&self) -> &Url {
582            panic!()
583        }
584
585        fn config_parent_overrides(&self) -> Option<&[cm_rust::ConfigOverride]> {
586            panic!()
587        }
588
589        fn policy_checker(&self) -> &GlobalPolicyChecker {
590            panic!()
591        }
592
593        fn component_id_index(&self) -> &component_id_index::Index {
594            panic!()
595        }
596
597        fn try_get_parent(
598            &self,
599        ) -> Result<ExtendedInstanceInterface<Self>, ComponentInstanceError> {
600            panic!()
601        }
602
603        async fn lock_resolved_state<'a>(
604            self: &'a Arc<Self>,
605        ) -> Result<Box<dyn ResolvedInstanceInterface<Component = Self> + 'a>, ComponentInstanceError>
606        {
607            panic!()
608        }
609
610        async fn component_sandbox(
611            self: &Arc<Self>,
612        ) -> Result<ComponentSandbox, ComponentInstanceError> {
613            panic!()
614        }
615    }
616
617    #[derive(Clone)]
618    struct TestErrorReporter {
619        reported: Arc<Mutex<bool>>,
620    }
621
622    impl TestErrorReporter {
623        fn new() -> Self {
624            Self { reported: Arc::new(Mutex::new(false)) }
625        }
626    }
627
628    #[async_trait]
629    impl ErrorReporter for TestErrorReporter {
630        async fn report(
631            &self,
632            _request: &RouteRequestErrorInfo,
633            _err: &RouterError,
634            _route_target: Arc<WeakInstanceToken>,
635        ) {
636            let mut reported = self.reported.lock();
637            if *reported {
638                panic!("report() was called twice");
639            }
640            *reported = true;
641        }
642    }
643
644    fn fake_component() -> Arc<FakeComponent> {
645        Arc::new(FakeComponent { moniker: Moniker::root() })
646    }
647
648    fn error_info() -> cm_rust::UseDecl {
649        UseBuilder::protocol().name("name").build()
650    }
651
652    #[fuchsia::test]
653    async fn success() {
654        let source = Arc::new(Data::String("hello".into()));
655        let base = Router::<Data>::new_ok(source);
656        let component = fake_component();
657        let proxy = base
658            .with_porcelain_with_default(CapabilityTypeName::Protocol)
659            .availability(Availability::Optional)
660            .target(&component)
661            .error_info(&error_info())
662            .error_reporter(TestErrorReporter::new())
663            .build();
664        let request = RouteRequest {
665            build_type_name: Some(CapabilityTypeName::Protocol.to_string()),
666            availability: Some(Availability::Optional.native_into_fidl()),
667            ..Default::default()
668        };
669
670        let capability = proxy.route(request, component.as_weak().into()).await.unwrap();
671        let capability = match capability {
672            Some(d) => d,
673            _ => panic!(),
674        };
675        assert_eq!(&*capability, &Data::String("hello".into()));
676    }
677
678    #[fuchsia::test]
679    async fn type_missing() {
680        let reporter = TestErrorReporter::new();
681        let reported = reporter.reported.clone();
682        let source = Data::String("hello".into());
683        let base = Router::<Data>::new_ok(source);
684        let component = fake_component();
685        let proxy = base
686            .with_porcelain_with_default(CapabilityTypeName::Protocol)
687            .availability(Availability::Optional)
688            .target(&component)
689            .error_info(&error_info())
690            .error_reporter(reporter)
691            .log_errors()
692            .build();
693        let request = RouteRequest {
694            availability: Some(Availability::Optional.native_into_fidl()),
695            ..Default::default()
696        };
697
698        let error = proxy.route(request, component.as_weak().into()).await.unwrap_err();
699        assert_matches!(
700            error,
701            RouterError::NotFound(err)
702            if matches!(
703                err.as_any().downcast_ref::<RoutingError>(),
704                Some(RoutingError::BedrockMissingCapabilityType {
705                    moniker,
706                    type_name,
707                }) if moniker == &Moniker::root().into() && type_name == "protocol"
708            )
709        );
710        assert!(*reported.lock());
711    }
712
713    #[fuchsia::test]
714    async fn type_mismatch() {
715        let reporter = TestErrorReporter::new();
716        let reported = reporter.reported.clone();
717        let source = Data::String("hello".into());
718        let base = Router::<Data>::new_ok(source);
719        let component = fake_component();
720        let proxy = base
721            .with_porcelain_with_default(CapabilityTypeName::Protocol)
722            .availability(Availability::Optional)
723            .target(&component)
724            .error_info(&error_info())
725            .error_reporter(reporter)
726            .log_errors()
727            .build();
728        let request = RouteRequest {
729            build_type_name: Some(CapabilityTypeName::Service.to_string()),
730            availability: Some(Availability::Optional.native_into_fidl()),
731            ..Default::default()
732        };
733
734        let error = proxy.route(request, component.as_weak().into()).await.unwrap_err();
735        assert_matches!(
736            error,
737            RouterError::NotFound(err)
738            if matches!(
739                err.as_any().downcast_ref::<RoutingError>(),
740                Some(RoutingError::BedrockWrongCapabilityType {
741                    moniker,
742                    expected,
743                    actual
744                }) if moniker == &Moniker::root().into()
745                    && expected == "protocol" && actual == "service"
746            )
747        );
748        assert!(*reported.lock());
749    }
750
751    #[fuchsia::test]
752    async fn availability_mismatch() {
753        let reporter = TestErrorReporter::new();
754        let reported = reporter.reported.clone();
755        let source = Data::String("hello".into());
756        let base = Router::<Data>::new_ok(source);
757        let component = fake_component();
758        let proxy = base
759            .with_porcelain_with_default(CapabilityTypeName::Protocol)
760            .availability(Availability::Optional)
761            .target(&component)
762            .error_info(&error_info())
763            .error_reporter(reporter)
764            .log_errors()
765            .build();
766        let request = RouteRequest {
767            build_type_name: Some(CapabilityTypeName::Protocol.to_string()),
768            availability: Some(Availability::Required.native_into_fidl()),
769            ..Default::default()
770        };
771
772        let error = proxy.route(request, component.as_weak().into()).await.unwrap_err();
773        assert_matches!(
774            error,
775            RouterError::NotFound(err)
776            if matches!(
777                err.as_any().downcast_ref::<RoutingError>(),
778                Some(RoutingError::AvailabilityRoutingError(
779                        crate::error::AvailabilityRoutingError::TargetHasStrongerAvailability {
780                        moniker
781                    }
782                )) if moniker == &Moniker::root().into()
783            )
784        );
785        assert!(*reported.lock());
786    }
787}