Skip to main content

camel_component_http/
bundle.rs

1use std::sync::Arc;
2
3use camel_component_api::{CamelError, ComponentBundle, ComponentRegistrar};
4
5use crate::static_config::HttpStaticConfig;
6use crate::static_endpoint::HttpStaticComponent;
7use crate::{HttpComponent, HttpsComponent, config::HttpConfig};
8
9pub struct HttpBundle {
10    config: HttpConfig,
11}
12
13impl ComponentBundle for HttpBundle {
14    fn config_key() -> &'static str {
15        "http"
16    }
17
18    fn from_toml(value: toml::Value) -> Result<Self, CamelError> {
19        let config: HttpConfig = value
20            .try_into()
21            .map_err(|e: toml::de::Error| CamelError::Config(e.to_string()))?;
22        Ok(Self { config })
23    }
24
25    fn register_all(self, ctx: &mut dyn ComponentRegistrar) {
26        ctx.register_component_dyn(Arc::new(HttpComponent::with_config(self.config.clone())));
27        ctx.register_component_dyn(Arc::new(HttpsComponent::with_config(self.config)));
28    }
29}
30
31// ---------------------------------------------------------------------------
32// HttpStaticBundle
33// ---------------------------------------------------------------------------
34
35/// Component bundle for the `http-static` scheme.
36///
37/// Loads configuration from `[default.components.http-static]` in Camel.toml
38/// and registers [`HttpStaticComponent`].
39#[derive(Debug)]
40pub struct HttpStaticBundle {
41    config: HttpStaticConfig,
42}
43
44impl ComponentBundle for HttpStaticBundle {
45    fn config_key() -> &'static str {
46        "http-static"
47    }
48
49    fn from_toml(value: toml::Value) -> Result<Self, CamelError> {
50        // Empty TOML section → use default config (dir will be validated at consumer start).
51        // This allows the bundle to register even when no [components.http-static] exists.
52        if let toml::Value::Table(ref table) = value
53            && table.is_empty()
54        {
55            return Ok(Self {
56                config: HttpStaticConfig::default(),
57            });
58        }
59        let config: HttpStaticConfig = value
60            .try_into()
61            .map_err(|e: toml::de::Error| CamelError::Config(e.to_string()))?;
62        Ok(Self { config })
63    }
64
65    fn register_all(self, ctx: &mut dyn ComponentRegistrar) {
66        ctx.register_component_dyn(Arc::new(HttpStaticComponent::with_config(self.config)));
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73
74    struct TestRegistrar {
75        schemes: Vec<String>,
76    }
77
78    impl camel_component_api::ComponentRegistrar for TestRegistrar {
79        fn register_component_dyn(
80            &mut self,
81            component: std::sync::Arc<dyn camel_component_api::Component>,
82        ) {
83            self.schemes.push(component.scheme().to_string());
84        }
85    }
86
87    #[test]
88    fn http_bundle_from_toml_empty_uses_defaults() {
89        let value: toml::Value = toml::from_str("").unwrap();
90        let result = HttpBundle::from_toml(value);
91        assert!(
92            result.is_ok(),
93            "empty HTTP toml must use defaults: {:?}",
94            result.err()
95        );
96    }
97
98    #[test]
99    fn http_bundle_from_toml_custom_timeout() {
100        let value: toml::Value = toml::from_str("connect_timeout_ms = 1000").unwrap();
101        let bundle = HttpBundle::from_toml(value).unwrap();
102        assert_eq!(bundle.config.connect_timeout_ms, 1000);
103    }
104
105    #[test]
106    fn http_bundle_from_toml_returns_error_on_invalid_config() {
107        let mut table = toml::map::Map::new();
108        table.insert(
109            "connect_timeout_ms".to_string(),
110            toml::Value::String("not-a-number".to_string()),
111        );
112
113        let result = HttpBundle::from_toml(toml::Value::Table(table));
114        assert!(result.is_err(), "expected Err on malformed config");
115        let err_msg = match result {
116            Err(err) => err.to_string(),
117            Ok(_) => panic!("expected Err on malformed config"),
118        };
119        assert!(
120            err_msg.contains("Configuration error"),
121            "expected CamelError::Config, got: {err_msg}"
122        );
123    }
124
125    #[test]
126    fn http_bundle_registers_expected_schemes() {
127        let bundle = HttpBundle::from_toml(toml::Value::Table(toml::map::Map::new())).unwrap();
128        let mut registrar = TestRegistrar { schemes: vec![] };
129
130        bundle.register_all(&mut registrar);
131
132        assert_eq!(registrar.schemes, vec!["http", "https"]);
133    }
134
135    // HttpStaticBundle tests
136
137    #[test]
138    fn http_static_bundle_config_key() {
139        assert_eq!(HttpStaticBundle::config_key(), "http-static");
140    }
141
142    #[test]
143    fn http_static_bundle_from_toml_empty_uses_defaults() {
144        let value: toml::Value = toml::from_str("").unwrap();
145        let result = HttpStaticBundle::from_toml(value);
146        assert!(
147            result.is_ok(),
148            "empty http-static toml must use defaults: {:?}",
149            result.err()
150        );
151        let bundle = result.unwrap();
152        assert!(bundle.config.dir.as_os_str().is_empty());
153        assert_eq!(bundle.config.port, 8080);
154    }
155
156    #[test]
157    fn http_static_bundle_from_toml_with_config() {
158        let toml_str = r#"
159            dir = "/app/spa/dist"
160            port = 3000
161            spaFallback = true
162            cacheControl = "public, max-age=3600"
163        "#;
164        let value: toml::Value = toml::from_str(toml_str).unwrap();
165        let bundle = HttpStaticBundle::from_toml(value).unwrap();
166        assert_eq!(bundle.config.dir, std::path::PathBuf::from("/app/spa/dist"));
167        assert_eq!(bundle.config.port, 3000);
168        assert!(bundle.config.spa_fallback);
169        assert_eq!(bundle.config.cache_control, "public, max-age=3600");
170    }
171
172    #[test]
173    fn http_static_bundle_from_toml_returns_error_on_invalid_config() {
174        let mut table = toml::map::Map::new();
175        table.insert(
176            "port".to_string(),
177            toml::Value::String("not-a-number".to_string()),
178        );
179
180        let result = HttpStaticBundle::from_toml(toml::Value::Table(table));
181        assert!(result.is_err(), "expected Err on malformed config");
182        let err_msg = match result {
183            Err(err) => err.to_string(),
184            Ok(_) => panic!("expected Err on malformed config"),
185        };
186        assert!(
187            err_msg.contains("Configuration error"),
188            "expected CamelError::Config, got: {err_msg}"
189        );
190    }
191
192    #[test]
193    fn http_static_bundle_registers_expected_scheme() {
194        let toml_str = r#"dir = "/tmp/static""#;
195        let value: toml::Value = toml::from_str(toml_str).unwrap();
196        let bundle = HttpStaticBundle::from_toml(value).unwrap();
197        let mut registrar = TestRegistrar { schemes: vec![] };
198
199        bundle.register_all(&mut registrar);
200
201        assert_eq!(registrar.schemes, vec!["http-static"]);
202    }
203}