1#[cfg(test)]
12mod tests;
13
14use std::env;
15use std::sync::Arc;
16use log::LevelFilter;
17use thiserror::Error;
18
19use crate::config::{Config, ConfigError, ConfigProvider, EnvConfigProvider, FileConfigProvider};
20use crate::router::{FilterConfig, PredicateRouter};
21use crate::{info_fmt, init_with_config, Filter, FilterFactory, ProxyError, ProxyServer, ServerConfig};
22use crate::core::ProxyCore;
23use crate::logging::config::LoggingConfig;
24
25#[derive(Error, Debug)]
27pub enum LoaderError {
28 #[error("configuration error: {0}")]
30 ConfigError(#[from] ConfigError),
31
32 #[error("proxy error: {0}")]
34 ProxyError(#[from] ProxyError),
35
36 #[error("IO error: {0}")]
38 IoError(#[from] std::io::Error),
39
40 #[error("{0}")]
42 Other(String),
43}
44
45#[derive(Debug, Default)]
47pub struct FoxyLoader {
48 config_builder: Option<Config>,
49 config_file_path: Option<String>,
50 use_env_vars: bool,
51 env_prefix: Option<String>,
52 custom_filters: Vec<Arc<dyn Filter>>,
53}
54
55
56
57impl FoxyLoader {
58 pub fn new() -> Self {
60 Self::default()
61 }
62
63 pub fn with_config(mut self, config: Config) -> Self {
65 self.config_builder = Some(config);
66 self
67 }
68
69 pub fn with_config_file(mut self, file_path: &str) -> Self {
71 self.config_file_path = Some(file_path.to_string());
72 self
73 }
74
75 pub fn with_env_vars(mut self) -> Self {
77 self.use_env_vars = true;
78 self
79 }
80
81 pub fn with_env_prefix(mut self, prefix: &str) -> Self {
83 self.env_prefix = Some(prefix.to_string());
84 self.use_env_vars = true;
85 self
86 }
87
88 pub fn with_provider<P: ConfigProvider + 'static>(self, provider: P) -> Self {
90 let config_builder = match self.config_builder {
91 Some(_) => Config::builder().with_provider(provider),
92 None => Config::builder().with_provider(provider),
93 };
94
95 Self {
96 config_builder: Some(config_builder.build()),
97 ..self
98 }
99 }
100
101 pub fn with_filter<F: Filter + 'static>(mut self, filter: F) -> Self {
103 self.custom_filters.push(Arc::new(filter));
104 self
105 }
106
107 pub async fn build(self) -> Result<Foxy, LoaderError> {
109 let config = if let Some(config) = self.config_builder {
111 config
112 } else {
113 let mut config_builder = Config::builder();
114
115 if self.use_env_vars {
117 let env_provider = match self.env_prefix {
118 Some(prefix) => EnvConfigProvider::new(&prefix),
119 None => EnvConfigProvider::default(),
120 };
121 config_builder = config_builder.with_provider(env_provider);
122 }
123
124 if let Some(file_path) = self.config_file_path {
126 match FileConfigProvider::new(&file_path) {
127 Ok(file_provider) => {
128 config_builder = config_builder.with_provider(file_provider);
129 },
130 Err(e) => {
131 return Err(LoaderError::ConfigError(e));
132 }
133 }
134 }
135
136 config_builder.build()
137 };
138
139 let config_arc = Arc::new(config);
140
141 let mut logging_config: LoggingConfig = config_arc.get("proxy.logging")
143 .unwrap_or(None)
144 .unwrap_or_default();
145
146 let level_str_from_env = env::var("RUST_LOG").ok();
148 let final_level_str = level_str_from_env.as_deref().unwrap_or(&logging_config.level);
149 let final_level_filter = final_level_str.parse::<LevelFilter>().unwrap_or(LevelFilter::Info);
150
151 logging_config.level = final_level_filter.to_string();
154
155 init_with_config(final_level_filter, &logging_config);
157
158 info_fmt!("Loader", "Foxy starting up");
159
160 #[cfg(feature = "opentelemetry")]
161 {
162 if let Ok(Some(otel_config)) = config_arc.get::<crate::opentelemetry::OpenTelemetryConfig>("proxy.opentelemetry") {
163 info_fmt!("Loader", "OpenTelemetry initialized with endpoint: {} and service name: {}",
164 otel_config.endpoint, otel_config.service_name);
165 }
166 }
167
168 let router = PredicateRouter::new(config_arc.clone()).await?;
170
171 let proxy_core = ProxyCore::new(config_arc.clone(), Arc::new(router)).await?;
173
174 let global_filters_config: Option<Vec<FilterConfig>> = config_arc.get("proxy.global_filters")?;
176
177 if let Some(global_filters) = global_filters_config {
178 for filter_config in global_filters {
179 let filter = FilterFactory::create_filter(
180 &filter_config.type_,
181 filter_config.config.clone(),
182 )?;
183 proxy_core.add_global_filter(filter).await;
184
185 info_fmt!("Loader", "Added global filter: {}", filter_config.type_);
186 }
187 }
188
189 for filter in self.custom_filters {
191 proxy_core.add_global_filter(filter).await;
192 }
193
194 let server_config: ServerConfig = config_arc.get_or_default("server", ServerConfig::default())?;
196
197 let proxy_server = ProxyServer::new(server_config, Arc::new(proxy_core));
199
200 Ok(Foxy {
202 config: config_arc,
203 server: proxy_server,
204 })
205 }
206}
207
208#[derive(Debug, Clone)]
210pub struct Foxy {
211 config: Arc<Config>,
212 server: ProxyServer,
213}
214
215impl Foxy {
216 pub fn loader() -> FoxyLoader {
218 FoxyLoader::new()
219 }
220
221 pub fn config(&self) -> &Config {
223 &self.config
224 }
225
226 pub async fn start(&self) -> Result<(), LoaderError> {
228 self.server.start().await.map_err(LoaderError::ProxyError)
229 }
230}