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