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