sandbox/
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, Dict, 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: Dict,
18}
19
20impl Request {
21    /// Clones the [`Request`] where the metadata [`Dict`] is a shallow copy. As a
22    /// result, the metadata [`Dict`] must not contain a nested [`Dict`] 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                    Ok(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::DirEntry> {
103    fn debug_typename() -> &'static str {
104        "DirEntryRouter"
105    }
106}
107impl CapabilityBound for Router<crate::Dict> {
108    fn debug_typename() -> &'static str {
109        "DictionaryRouter"
110    }
111}
112
113impl CapabilityBound for Router<crate::DirConnector> {
114    fn debug_typename() -> &'static str {
115        "DirConnectorRouter"
116    }
117}
118
119impl<T: CapabilityBound> fmt::Debug for Router<T> {
120    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121        // TODO(https://fxbug.dev/329680070): Require `Debug` on `Routable` trait.
122        f.debug_struct("Router").field("routable", &"[some routable object]").finish()
123    }
124}
125
126/// Syntax sugar within the framework to express custom routing logic using a function
127/// that takes a request and returns such future.
128impl<T: CapabilityBound, F> Routable<T> for F
129where
130    F: Fn(
131            Option<Request>,
132            bool,
133            WeakInstanceToken,
134        ) -> BoxFuture<'static, Result<RouterResponse<T>, RouterError>>
135        + Send
136        + Sync
137        + 'static,
138{
139    // We use the desugared form of `async_trait` to avoid unnecessary boxing.
140    fn route<'a, 'b>(
141        &'a self,
142        request: Option<Request>,
143        debug: bool,
144        target: WeakInstanceToken,
145    ) -> BoxFuture<'b, Result<RouterResponse<T>, RouterError>>
146    where
147        'a: 'b,
148        Self: 'b,
149    {
150        self(request, debug, target)
151    }
152}
153
154#[async_trait]
155impl<T: CapabilityBound> Routable<T> for Router<T> {
156    async fn route(
157        &self,
158        request: Option<Request>,
159        debug: bool,
160        target: WeakInstanceToken,
161    ) -> Result<RouterResponse<T>, RouterError> {
162        Router::route(self, request, debug, target).await
163    }
164}
165
166impl<T: CapabilityBound> Router<T> {
167    /// Package a [`Routable`] object into a [`Router`].
168    pub fn new(routable: impl Routable<T> + 'static) -> Self {
169        Self { routable: Arc::new(routable) }
170    }
171
172    /// Creates a router that will always fail a request with the provided error.
173    pub fn new_error(error: impl Into<RouterError>) -> Self {
174        let v: RouterError = error.into();
175        Self::new(ErrRouter { v })
176    }
177
178    /// Creates a router that will always return the given debug info.
179    pub fn new_debug(data: impl Into<Data>) -> Self {
180        let v: Data = data.into();
181        Self::new(DebugRouter { v })
182    }
183
184    /// Obtain a capability from this router, following the description in `request`.
185    pub async fn route(
186        &self,
187        request: Option<Request>,
188        debug: bool,
189        target: WeakInstanceToken,
190    ) -> Result<RouterResponse<T>, RouterError> {
191        self.routable.route(request, debug, target).await
192    }
193}
194
195impl<T: Clone + CapabilityBound> Router<T> {
196    /// Creates a router that will always resolve with the provided capability.
197    // TODO: Should this require debug info?
198    pub fn new_ok(c: impl Into<T>) -> Self {
199        let v: T = c.into();
200        Self::new(OkRouter { v })
201    }
202}
203
204#[derive(Clone)]
205struct OkRouter<T: Clone + CapabilityBound> {
206    v: T,
207}
208
209#[async_trait]
210impl<T: Clone + CapabilityBound> Routable<T> for OkRouter<T> {
211    async fn route(
212        &self,
213        _request: Option<Request>,
214        debug: bool,
215        _target: WeakInstanceToken,
216    ) -> Result<RouterResponse<T>, RouterError> {
217        assert!(!debug, "OkRouter does not handle debug routes");
218        Ok(RouterResponse::Capability(self.v.clone()))
219    }
220}
221
222#[derive(Clone)]
223struct DebugRouter {
224    v: Data,
225}
226
227#[async_trait]
228impl<T: CapabilityBound> Routable<T> for DebugRouter {
229    async fn route(
230        &self,
231        _request: Option<Request>,
232        _debug: bool,
233        _target: WeakInstanceToken,
234    ) -> Result<RouterResponse<T>, RouterError> {
235        Ok(RouterResponse::Debug(self.v.clone()))
236    }
237}
238
239#[derive(Clone)]
240struct ErrRouter {
241    v: RouterError,
242}
243
244#[async_trait]
245impl<T: CapabilityBound> Routable<T> for ErrRouter {
246    async fn route(
247        &self,
248        _request: Option<Request>,
249        _debug: bool,
250        _target: WeakInstanceToken,
251    ) -> Result<RouterResponse<T>, RouterError> {
252        Err(self.v.clone())
253    }
254}