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 #[serde(default)]
69 pub otel: Option<OtelCamelConfig>,
70}
71
72#[derive(Debug, Clone, Deserialize)]
74pub struct OtelCamelConfig {
75 #[serde(default)]
77 pub enabled: bool,
78
79 #[serde(default = "default_otel_endpoint")]
81 pub endpoint: String,
82
83 #[serde(default = "default_otel_service_name")]
85 pub service_name: String,
86
87 #[serde(default = "default_otel_log_level")]
89 pub log_level: String,
90}
91
92#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
93pub struct SupervisionCamelConfig {
94 pub max_attempts: Option<u32>,
96
97 #[serde(default = "default_initial_delay_ms")]
99 pub initial_delay_ms: u64,
100
101 #[serde(default = "default_backoff_multiplier")]
103 pub backoff_multiplier: f64,
104
105 #[serde(default = "default_max_delay_ms")]
107 pub max_delay_ms: u64,
108}
109
110impl Default for SupervisionCamelConfig {
111 fn default() -> Self {
112 Self {
113 max_attempts: Some(5),
114 initial_delay_ms: 1000,
115 backoff_multiplier: 2.0,
116 max_delay_ms: 60000,
117 }
118 }
119}
120
121impl SupervisionCamelConfig {
122 pub fn into_supervision_config(self) -> camel_api::SupervisionConfig {
124 camel_api::SupervisionConfig {
125 max_attempts: self.max_attempts,
126 initial_delay: Duration::from_millis(self.initial_delay_ms),
127 backoff_multiplier: self.backoff_multiplier,
128 max_delay: Duration::from_millis(self.max_delay_ms),
129 }
130 }
131}
132
133fn default_log_level() -> String {
134 "INFO".to_string()
135}
136fn default_timeout_ms() -> u64 {
137 5000
138}
139fn default_timer_period() -> u64 {
140 1000
141}
142fn default_http_connect_timeout() -> u64 {
143 5000
144}
145fn default_http_max_connections() -> usize {
146 100
147}
148fn default_metrics_port() -> u16 {
149 9090
150}
151
152fn default_otel_endpoint() -> String {
153 "http://localhost:4317".to_string()
154}
155fn default_otel_service_name() -> String {
156 "rust-camel".to_string()
157}
158fn default_otel_log_level() -> String {
159 "info".to_string()
160}
161
162fn default_initial_delay_ms() -> u64 {
163 1000
164}
165
166fn default_backoff_multiplier() -> f64 {
167 2.0
168}
169
170fn default_max_delay_ms() -> u64 {
171 60000
172}
173
174fn merge_toml_values(base: &mut toml::Value, overlay: &toml::Value) {
177 match (base, overlay) {
178 (toml::Value::Table(base_table), toml::Value::Table(overlay_table)) => {
179 for (key, value) in overlay_table {
180 if let Some(base_value) = base_table.get_mut(key) {
181 merge_toml_values(base_value, value);
183 } else {
184 base_table.insert(key.clone(), value.clone());
186 }
187 }
188 }
189 (base, overlay) => {
191 *base = overlay.clone();
192 }
193 }
194}
195
196impl CamelConfig {
197 pub fn from_file(path: &str) -> Result<Self, ConfigError> {
198 Self::from_file_with_profile(path, None)
199 }
200
201 pub fn from_file_with_env(path: &str) -> Result<Self, ConfigError> {
202 Self::from_file_with_profile_and_env(path, None)
203 }
204
205 pub fn from_file_with_profile(path: &str, profile: Option<&str>) -> Result<Self, ConfigError> {
206 let env_profile = env::var("CAMEL_PROFILE").ok();
208 let profile = profile.or(env_profile.as_deref());
209
210 let content = std::fs::read_to_string(path)
212 .map_err(|e| ConfigError::Message(format!("Failed to read config file: {}", e)))?;
213 let mut config_value: toml::Value = toml::from_str(&content)
214 .map_err(|e| ConfigError::Message(format!("Failed to parse TOML: {}", e)))?;
215
216 if let Some(p) = profile {
218 let default_value = config_value.get("default").cloned();
220
221 let profile_value = config_value.get(p).cloned();
223
224 if let (Some(mut base), Some(overlay)) = (default_value, profile_value) {
225 merge_toml_values(&mut base, &overlay);
227
228 config_value = base;
230 } else if let Some(profile_val) = config_value.get(p).cloned() {
231 config_value = profile_val;
233 } else {
234 return Err(ConfigError::Message(format!("Unknown profile: {}", p)));
235 }
236 } else {
237 if let Some(default_val) = config_value.get("default").cloned() {
239 config_value = default_val;
240 }
241 }
242
243 let merged_toml = toml::to_string(&config_value).map_err(|e| {
245 ConfigError::Message(format!("Failed to serialize merged config: {}", e))
246 })?;
247
248 let config = Config::builder()
249 .add_source(config::File::from_str(
250 &merged_toml,
251 config::FileFormat::Toml,
252 ))
253 .build()?;
254
255 config.try_deserialize()
256 }
257
258 pub fn from_file_with_profile_and_env(
259 path: &str,
260 profile: Option<&str>,
261 ) -> Result<Self, ConfigError> {
262 let env_profile = env::var("CAMEL_PROFILE").ok();
264 let profile = profile.or(env_profile.as_deref());
265
266 let content = std::fs::read_to_string(path)
268 .map_err(|e| ConfigError::Message(format!("Failed to read config file: {}", e)))?;
269 let mut config_value: toml::Value = toml::from_str(&content)
270 .map_err(|e| ConfigError::Message(format!("Failed to parse TOML: {}", e)))?;
271
272 if let Some(p) = profile {
274 let default_value = config_value.get("default").cloned();
276
277 let profile_value = config_value.get(p).cloned();
279
280 if let (Some(mut base), Some(overlay)) = (default_value, profile_value) {
281 merge_toml_values(&mut base, &overlay);
283
284 config_value = base;
286 } else if let Some(profile_val) = config_value.get(p).cloned() {
287 config_value = profile_val;
289 } else {
290 return Err(ConfigError::Message(format!("Unknown profile: {}", p)));
291 }
292 } else {
293 if let Some(default_val) = config_value.get("default").cloned() {
295 config_value = default_val;
296 }
297 }
298
299 let merged_toml = toml::to_string(&config_value).map_err(|e| {
301 ConfigError::Message(format!("Failed to serialize merged config: {}", e))
302 })?;
303
304 let config = Config::builder()
305 .add_source(config::File::from_str(
306 &merged_toml,
307 config::FileFormat::Toml,
308 ))
309 .add_source(config::Environment::with_prefix("CAMEL").try_parsing(true))
310 .build()?;
311
312 config.try_deserialize()
313 }
314
315 pub fn from_env_or_default() -> Result<Self, ConfigError> {
316 let path = env::var("CAMEL_CONFIG_FILE").unwrap_or_else(|_| "Camel.toml".to_string());
317
318 Self::from_file(&path)
319 }
320}