routing/bedrock/
use_dictionary_router.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::capability_source::CapabilitySource;
6use crate::error::RoutingError;
7use async_trait::async_trait;
8use futures::StreamExt;
9use futures::stream::FuturesUnordered;
10use router_error::RouterError;
11use sandbox::{Dict, Request, Routable, Router, RouterResponse};
12
13/// Given an original dictionary and a handful of additional dictionary routers, produces a router
14/// that when invoked will route all the additional routers, merge everything together into a
15/// single dictionary, and return that.
16pub struct UseDictionaryRouter {
17    original_dictionary: Dict,
18    dictionary_routers: Vec<Router<Dict>>,
19    capability_source: CapabilitySource,
20}
21
22impl UseDictionaryRouter {
23    pub fn new(
24        original_dictionary: Dict,
25        dictionary_routers: Vec<Router<Dict>>,
26        capability_source: CapabilitySource,
27    ) -> Router<Dict> {
28        Router::new(Self { original_dictionary, dictionary_routers, capability_source })
29    }
30}
31
32#[async_trait]
33impl Routable<Dict> for UseDictionaryRouter {
34    async fn route(
35        &self,
36        request: Option<Request>,
37        debug: bool,
38    ) -> Result<RouterResponse<Dict>, RouterError> {
39        if debug {
40            return Ok(RouterResponse::Debug(
41                self.capability_source
42                    .clone()
43                    .try_into()
44                    .expect("failed to serialize capability source"),
45            ));
46        }
47        let mut futures_unordered = FuturesUnordered::new();
48        for dictionary_router in self.dictionary_routers.iter() {
49            let request = request.as_ref().and_then(|r| r.try_clone().ok());
50            futures_unordered.push(dictionary_router.route(request, false));
51        }
52        let resulting_dictionary = self.original_dictionary.shallow_copy().unwrap();
53        while let Some(route_result) = futures_unordered.next().await {
54            match route_result {
55                Ok(RouterResponse::Capability(other_dictionary)) => {
56                    let maybe_conflicting_name =
57                        resulting_dictionary.follow_updates_from(other_dictionary).await;
58                    if let Some(conflicting_name) = maybe_conflicting_name {
59                        return Err(RoutingError::ConflictingDictionaryEntries {
60                            moniker: self.capability_source.source_moniker(),
61                            conflicting_name,
62                        }
63                        .into());
64                    }
65                }
66                Ok(RouterResponse::Unavailable) => (),
67                Ok(RouterResponse::Debug(_)) => {
68                    panic!("got debug response when we didn't request one")
69                }
70                Err(_e) => {
71                    // Errors are already logged by this point by the WithPorcelain router.
72                    // Specifically, the routers in `dictionary_routers` are assembled by
73                    // `crate::bedrock::sandbox_construction::extend_dict_with_use`, which does
74                    // this wrapping.
75                }
76            }
77        }
78        Ok(resulting_dictionary.into())
79    }
80}