1use camel_core::config::TracerConfig;
2use config::{Config, ConfigError};
3use serde::{Deserialize, Serialize};
4use std::env;
5use std::time::Duration;
6
7#[derive(Debug, Clone, Deserialize)]
8pub struct CamelConfig {
9 #[serde(default)]
10 pub routes: Vec<String>,
11
12 #[serde(default)]
15 pub watch: bool,
16
17 #[serde(default = "default_log_level")]
18 pub log_level: String,
19
20 #[serde(default = "default_timeout_ms")]
21 pub timeout_ms: u64,
22
23 #[serde(default)]
24 pub components: ComponentsConfig,
25
26 #[serde(default)]
27 pub observability: ObservabilityConfig,
28
29 #[serde(default)]
30 pub supervision: Option<SupervisionCamelConfig>,
31}
32
33#[derive(Debug, Clone, Deserialize, Default, PartialEq)]
34pub struct ComponentsConfig {
35 #[serde(default)]
36 pub timer: Option<TimerConfig>,
37
38 #[serde(default)]
39 pub http: Option<HttpConfig>,
40}
41
42#[derive(Debug, Clone, Deserialize, PartialEq)]
43pub struct TimerConfig {
44 #[serde(default = "default_timer_period")]
45 pub period: u64,
46}
47
48#[derive(Debug, Clone, Deserialize, PartialEq)]
49pub struct HttpConfig {
50 #[serde(default = "default_http_connect_timeout")]
51 pub connect_timeout_ms: u64,
52
53 #[serde(default = "default_http_max_connections")]
54 pub max_connections: usize,
55}
56
57#[derive(Debug, Clone, Deserialize, Default)]
58pub struct ObservabilityConfig {
59 #[serde(default)]
60 pub metrics_enabled: bool,
61
62 #[serde(default = "default_metrics_port")]
63 pub metrics_port: u16,
64
65 #[serde(default)]
66 pub tracer: TracerConfig,
67}
68
69#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
70pub struct SupervisionCamelConfig {
71 pub max_attempts: Option<u32>,
73
74 #[serde(default = "default_initial_delay_ms")]
76 pub initial_delay_ms: u64,
77
78 #[serde(default = "default_backoff_multiplier")]
80 pub backoff_multiplier: f64,
81
82 #[serde(default = "default_max_delay_ms")]
84 pub max_delay_ms: u64,
85}
86
87impl Default for SupervisionCamelConfig {
88 fn default() -> Self {
89 Self {
90 max_attempts: Some(5),
91 initial_delay_ms: 1000,
92 backoff_multiplier: 2.0,
93 max_delay_ms: 60000,
94 }
95 }
96}
97
98impl SupervisionCamelConfig {
99 pub fn into_supervision_config(self) -> camel_api::SupervisionConfig {
101 camel_api::SupervisionConfig {
102 max_attempts: self.max_attempts,
103 initial_delay: Duration::from_millis(self.initial_delay_ms),
104 backoff_multiplier: self.backoff_multiplier,
105 max_delay: Duration::from_millis(self.max_delay_ms),
106 }
107 }
108}
109
110fn default_log_level() -> String {
111 "INFO".to_string()
112}
113fn default_timeout_ms() -> u64 {
114 5000
115}
116fn default_timer_period() -> u64 {
117 1000
118}
119fn default_http_connect_timeout() -> u64 {
120 5000
121}
122fn default_http_max_connections() -> usize {
123 100
124}
125fn default_metrics_port() -> u16 {
126 9090
127}
128
129fn default_initial_delay_ms() -> u64 {
130 1000
131}
132
133fn default_backoff_multiplier() -> f64 {
134 2.0
135}
136
137fn default_max_delay_ms() -> u64 {
138 60000
139}
140
141fn merge_toml_values(base: &mut toml::Value, overlay: &toml::Value) {
144 match (base, overlay) {
145 (toml::Value::Table(base_table), toml::Value::Table(overlay_table)) => {
146 for (key, value) in overlay_table {
147 if let Some(base_value) = base_table.get_mut(key) {
148 merge_toml_values(base_value, value);
150 } else {
151 base_table.insert(key.clone(), value.clone());
153 }
154 }
155 }
156 (base, overlay) => {
158 *base = overlay.clone();
159 }
160 }
161}
162
163impl CamelConfig {
164 pub fn from_file(path: &str) -> Result<Self, ConfigError> {
165 Self::from_file_with_profile(path, None)
166 }
167
168 pub fn from_file_with_env(path: &str) -> Result<Self, ConfigError> {
169 Self::from_file_with_profile_and_env(path, None)
170 }
171
172 pub fn from_file_with_profile(path: &str, profile: Option<&str>) -> Result<Self, ConfigError> {
173 let env_profile = env::var("CAMEL_PROFILE").ok();
175 let profile = profile.or(env_profile.as_deref());
176
177 let content = std::fs::read_to_string(path)
179 .map_err(|e| ConfigError::Message(format!("Failed to read config file: {}", e)))?;
180 let mut config_value: toml::Value = toml::from_str(&content)
181 .map_err(|e| ConfigError::Message(format!("Failed to parse TOML: {}", e)))?;
182
183 if let Some(p) = profile {
185 let default_value = config_value.get("default").cloned();
187
188 let profile_value = config_value.get(p).cloned();
190
191 if let (Some(mut base), Some(overlay)) = (default_value, profile_value) {
192 merge_toml_values(&mut base, &overlay);
194
195 config_value = base;
197 } else if let Some(profile_val) = config_value.get(p).cloned() {
198 config_value = profile_val;
200 } else {
201 return Err(ConfigError::Message(format!("Unknown profile: {}", p)));
202 }
203 } else {
204 if let Some(default_val) = config_value.get("default").cloned() {
206 config_value = default_val;
207 }
208 }
209
210 let merged_toml = toml::to_string(&config_value).map_err(|e| {
212 ConfigError::Message(format!("Failed to serialize merged config: {}", e))
213 })?;
214
215 let config = Config::builder()
216 .add_source(config::File::from_str(
217 &merged_toml,
218 config::FileFormat::Toml,
219 ))
220 .build()?;
221
222 config.try_deserialize()
223 }
224
225 pub fn from_file_with_profile_and_env(
226 path: &str,
227 profile: Option<&str>,
228 ) -> Result<Self, ConfigError> {
229 let env_profile = env::var("CAMEL_PROFILE").ok();
231 let profile = profile.or(env_profile.as_deref());
232
233 let content = std::fs::read_to_string(path)
235 .map_err(|e| ConfigError::Message(format!("Failed to read config file: {}", e)))?;
236 let mut config_value: toml::Value = toml::from_str(&content)
237 .map_err(|e| ConfigError::Message(format!("Failed to parse TOML: {}", e)))?;
238
239 if let Some(p) = profile {
241 let default_value = config_value.get("default").cloned();
243
244 let profile_value = config_value.get(p).cloned();
246
247 if let (Some(mut base), Some(overlay)) = (default_value, profile_value) {
248 merge_toml_values(&mut base, &overlay);
250
251 config_value = base;
253 } else if let Some(profile_val) = config_value.get(p).cloned() {
254 config_value = profile_val;
256 } else {
257 return Err(ConfigError::Message(format!("Unknown profile: {}", p)));
258 }
259 } else {
260 if let Some(default_val) = config_value.get("default").cloned() {
262 config_value = default_val;
263 }
264 }
265
266 let merged_toml = toml::to_string(&config_value).map_err(|e| {
268 ConfigError::Message(format!("Failed to serialize merged config: {}", e))
269 })?;
270
271 let config = Config::builder()
272 .add_source(config::File::from_str(
273 &merged_toml,
274 config::FileFormat::Toml,
275 ))
276 .add_source(config::Environment::with_prefix("CAMEL").try_parsing(true))
277 .build()?;
278
279 config.try_deserialize()
280 }
281
282 pub fn from_env_or_default() -> Result<Self, ConfigError> {
283 let path = env::var("CAMEL_CONFIG_FILE").unwrap_or_else(|_| "Camel.toml".to_string());
284
285 Self::from_file(&path)
286 }
287}