Skip to main content

runtime_capabilities/
router.rs

1// Copyright 2024 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::{Capability, CapabilityBound, Data, Dictionary, WeakInstanceToken};
6use async_trait::async_trait;
7use futures::future::BoxFuture;
8use router_error::RouterError;
9use std::fmt;
10use std::fmt::Debug;
11use std::sync::Arc;
12
13/// [`Request`] contains metadata around how to obtain a capability.
14#[derive(Debug)]
15pub struct Request {
16    /// Metadata associated with the request.
17    pub metadata: Dictionary,
18}
19
20impl Request {
21    /// Clones the [`Request`] where the metadata [`Dictionary`] is a shallow copy. As a
22    /// result, the metadata [`Dictionary`] must not contain a nested [`Dictionary`] otherwise a
23    /// [`RouterError::InvalidArgs`] error will be returned.
24    pub fn try_clone(&self) -> Result<Self, RouterError> {
25        self.metadata
26            .enumerate()
27            .find_map(|(_, v)| {
28                match v {
29                    // Since Dictionaries are shallow copied, throw an error if
30                    // there is a nested Dictionary.
31                    Capability::Dictionary(_) => Some(Err::<Self, _>(RouterError::InvalidArgs)),
32                    _ => None,
33                }
34            })
35            .transpose()?;
36        let metadata = self.metadata.shallow_copy().map_err(|()| RouterError::InvalidArgs)?;
37        Ok(Self { metadata })
38    }
39}
40
41/// Types that implement [`Routable`] let the holder asynchronously request capabilities
42/// from them.
43#[async_trait]
44pub trait Routable<T>: Send + Sync
45where
46    T: CapabilityBound,
47{
48    async fn route(
49        &self,
50        request: Option<Request>,
51        debug: bool,
52        // A reference to the requesting component.
53        target: WeakInstanceToken,
54    ) -> Result<RouterResponse<T>, RouterError>;
55}
56
57/// Response of a [Router] request.
58#[derive(Debug)]
59pub enum RouterResponse<T: CapabilityBound> {
60    /// Routing succeeded and returned this capability.
61    Capability(T),
62
63    /// Routing succeeded, but the capability was marked unavailable.
64    Unavailable,
65
66    /// Routing succeeded in debug mode, `Data` contains the debug data.
67    Debug(Data),
68}
69
70impl<T: CapabilityBound> From<T> for RouterResponse<T> {
71    fn from(val: T) -> Self {
72        Self::Capability(val)
73    }
74}
75
76/// A [`Router`] is a capability that lets the holder obtain other capabilities
77/// asynchronously. [`Router`] is the object capability representation of
78/// [`Routable`].
79///
80/// During routing, a request usually traverses through the component topology,
81/// passing through several routers, ending up at some router that will fulfill
82/// the request instead of forwarding it upstream.
83///
84/// [`Router`] differs from [`Router`] in that it is parameterized on the capability
85/// type `T`. Instead of a [`Capability`], [`Router`] returns a [`RouterResponse`].
86/// [`Router`] will supersede [`Router`].
87#[derive(Clone)]
88pub struct Router<T: CapabilityBound> {
89    routable: Arc<dyn Routable<T>>,
90}
91
92impl CapabilityBound for Router<crate::Connector> {
93    fn debug_typename() -> &'static str {
94        "ConnectorRouter"
95    }
96}
97impl CapabilityBound for Router<crate::Data> {
98    fn debug_typename() -> &'static str {
99        "DataRouter"
100    }
101}
102impl CapabilityBound for Router<crate::Dictionary> {
103    fn debug_typename() -> &'static str {
104        "DictionaryRouter"
105    }
106}
107
108impl CapabilityBound for Router<crate::DirConnector> {
109    fn debug_typename() -> &'static str {
110        "DirConnectorRouter"
111    }
112}
113
114impl<T: CapabilityBound> fmt::Debug for Router<T> {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        // TODO(https://fxbug.dev/329680070): Require `Debug` on `Routable` trait.
117        f.debug_struct("Router").field("routable", &"[some routable object]").finish()
118    }
119}
120
121/// Syntax sugar within the framework to express custom routing logic using a function
122/// that takes a request and returns such future.
123impl<T: CapabilityBound, F> Routable<T> for F
124where
125    F: Fn(
126            Option<Request>,
127            bool,
128            WeakInstanceToken,
129        ) -> BoxFuture<'static, Result<RouterResponse<T>, RouterError>>
130        + Send
131        + Sync
132        + 'static,
133{
134    // We use the desugared form of `async_trait` to avoid unnecessary boxing.
135    fn route<'a, 'b>(
136        &'a self,
137        request: Option<Request>,
138        debug: bool,
139        target: WeakInstanceToken,
140    ) -> BoxFuture<'b, Result<RouterResponse<T>, RouterError>>
141    where
142        'a: 'b,
143        Self: 'b,
144    {
145        self(request, debug, target)
146    }
147}
148
149#[async_trait]
150impl<T: CapabilityBound> Routable<T> for Router<T> {
151    async fn route(
152        &self,
153        request: Option<Request>,
154        debug: bool,
155        target: WeakInstanceToken,
156    ) -> Result<RouterResponse<T>, RouterError> {
157        Router::route(self, request, debug, target).await
158    }
159}
160
161impl<T: CapabilityBound> Router<T> {
162    /// Package a [`Routable`] object into a [`Router`].
163    pub fn new(routable: impl Routable<T> + 'static) -> Self {
164        Self { routable: Arc::new(routable) }
165    }
166
167    /// Creates a router that will always fail a request with the provided error.
168    pub fn new_error(error: impl Into<RouterError>) -> Self {
169        let v: RouterError = error.into();
170        Self::new(ErrRouter { v })
171    }
172
173    /// Creates a router that will always return the given debug info.
174    pub fn new_debug(data: impl Into<Data>) -> Self {
175        let v: Data = data.into();
176        Self::new(DebugRouter { v })
177    }
178
179    /// Obtain a capability from this router, following the description in `request`.
180    pub async fn route(
181        &self,
182        request: Option<Request>,
183        debug: bool,
184        target: WeakInstanceToken,
185    ) -> Result<RouterResponse<T>, RouterError> {
186        self.routable.route(request, debug, target).await
187    }
188}
189
190impl<T: Clone + CapabilityBound> Router<T> {
191    /// Creates a router that will always resolve with the provided capability.
192    // TODO: Should this require debug info?
193    pub fn new_ok(c: impl Into<T>) -> Self {
194        let v: T = c.into();
195        Self::new(OkRouter { v })
196    }
197}
198
199#[derive(Clone)]
200struct OkRouter<T: Clone + CapabilityBound> {
201    v: T,
202}
203
204#[async_trait]
205impl<T: Clone + CapabilityBound> Routable<T> for OkRouter<T> {
206    async fn route(
207        &self,
208        _request: Option<Request>,
209        debug: bool,
210        _target: WeakInstanceToken,
211    ) -> Result<RouterResponse<T>, RouterError> {
212        assert!(!debug, "OkRouter does not handle debug routes");
213        Ok(RouterResponse::Capability(self.v.clone()))
214    }
215}
216
217#[derive(Clone)]
218struct DebugRouter {
219    v: Data,
220}
221
222#[async_trait]
223impl<T: CapabilityBound> Routable<T> for DebugRouter {
224    async fn route(
225        &self,
226        _request: Option<Request>,
227        _debug: bool,
228        _target: WeakInstanceToken,
229    ) -> Result<RouterResponse<T>, RouterError> {
230        Ok(RouterResponse::Debug(self.v.clone()))
231    }
232}
233
234#[derive(Clone)]
235struct ErrRouter {
236    v: RouterError,
237}
238
239#[async_trait]
240impl<T: CapabilityBound> Routable<T> for ErrRouter {
241    async fn route(
242        &self,
243        _request: Option<Request>,
244        _debug: bool,
245        _target: WeakInstanceToken,
246    ) -> Result<RouterResponse<T>, RouterError> {
247        Err(self.v.clone())
248    }
249}