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