Skip to main content

camel_component_http/
registry.rs

1use std::collections::HashMap;
2use std::path::PathBuf;
3use std::sync::Arc;
4
5use camel_component_api::CamelError;
6use tokio::sync::{RwLock, mpsc};
7use tower_http::services::ServeDir;
8
9use crate::RequestEnvelope;
10
11/// Discriminates between a plain static file mount and
12/// a mount that also performs SPA‑style fallback to index.html.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum MountMode {
15    Static,
16    Spa,
17}
18
19#[allow(dead_code)]
20pub struct StaticMount {
21    pub mount_path: String,
22    pub mode: MountMode,
23    pub dir: PathBuf,
24    pub cache_control: String,
25    pub error_pages: HashMap<u16, PathBuf>,
26    pub serve_dir: ServeDir,
27}
28
29impl std::fmt::Debug for StaticMount {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        f.debug_struct("StaticMount")
32            .field("mount_path", &self.mount_path)
33            .field("mode", &self.mode)
34            .field("dir", &self.dir)
35            .field("cache_control", &self.cache_control)
36            .field("error_pages", &self.error_pages)
37            .finish_non_exhaustive()
38    }
39}
40
41pub(crate) struct HttpRouteRegistryInner {
42    pub api_routes: HashMap<String, mpsc::Sender<RequestEnvelope>>,
43    pub mounts: Vec<StaticMount>,
44}
45
46impl std::fmt::Debug for HttpRouteRegistryInner {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        f.debug_struct("HttpRouteRegistryInner")
49            .field("api_routes", &self.api_routes.keys())
50            .field("mounts", &self.mounts.len())
51            .finish()
52    }
53}
54
55#[derive(Clone)]
56pub struct HttpRouteRegistry {
57    pub(crate) inner: Arc<RwLock<HttpRouteRegistryInner>>,
58}
59
60impl std::fmt::Debug for HttpRouteRegistry {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        f.debug_struct("HttpRouteRegistry").finish_non_exhaustive()
63    }
64}
65
66impl Default for HttpRouteRegistry {
67    fn default() -> Self {
68        Self::new()
69    }
70}
71
72impl HttpRouteRegistry {
73    pub fn new() -> Self {
74        Self {
75            inner: Arc::new(RwLock::new(HttpRouteRegistryInner {
76                api_routes: HashMap::new(),
77                mounts: Vec::new(),
78            })),
79        }
80    }
81
82    pub async fn register_api_route(&self, path: String, sender: mpsc::Sender<RequestEnvelope>) {
83        let mut inner = self.inner.write().await;
84        inner.api_routes.insert(path, sender);
85    }
86
87    pub async fn unregister_api_route(&self, path: &str) {
88        let mut inner = self.inner.write().await;
89        inner.api_routes.remove(path);
90    }
91
92    /// Register a static mount. Duplicate detection is by `mount_path`
93    /// only — every mount on a given port must have a unique prefix.
94    ///
95    /// A single SPA mount is still the convention, but it is no longer
96    /// enforced structurally; the dispatch loop treats all mounts
97    /// uniformly (sorted by longest prefix) and uses `mode` to decide
98    /// whether to attempt SPA-fallback after ServeDir fails.
99    #[allow(dead_code)]
100    pub async fn register_static_mount(&self, mount: StaticMount) -> Result<(), CamelError> {
101        let mut inner = self.inner.write().await;
102        if inner
103            .mounts
104            .iter()
105            .any(|m| m.mount_path == mount.mount_path)
106        {
107            return Err(CamelError::Config(format!(
108                "duplicate static mount path '{}' on this port",
109                mount.mount_path
110            )));
111        }
112        inner.mounts.push(mount);
113        Ok(())
114    }
115
116    /// Unregister a static mount by its unique `mount_path`.
117    #[allow(dead_code)]
118    pub async fn unregister_static_mount(&self, mount_path: &str) {
119        let mut inner = self.inner.write().await;
120        inner.mounts.retain(|m| m.mount_path != mount_path);
121    }
122}