1#[cfg(test)]
12mod tests;
13
14use std::collections::HashMap;
15use std::env;
16use std::sync::Arc;
17use log::LevelFilter;
18use serde_json::Value;
19use thiserror::Error;
20
21use crate::config::{Config, ConfigError, ConfigProvider, EnvConfigProvider, FileConfigProvider};
22use crate::router::{FilterConfig, PredicateRouter, RouteConfig};
23use crate::{info_fmt, init_with_config, Filter, FilterFactory, ProxyError, ProxyServer, ServerConfig};
24use crate::core::ProxyCore;
25use crate::logging::config::LoggingConfig;
26
27#[derive(Error, Debug)]
29pub enum LoaderError {
30 #[error("configuration error: {0}")]
32 ConfigError(#[from] ConfigError),
33
34 #[error("proxy error: {0}")]
36 ProxyError(#[from] ProxyError),
37
38 #[error("IO error: {0}")]
40 IoError(#[from] std::io::Error),
41
42 #[error("{0}")]
44 Other(String),
45}
46
47#[derive(Debug)]
49pub struct FoxyLoader {
50 config_builder: Option<Config>,
51 config_file_path: Option<String>,
52 use_env_vars: bool,
53 env_prefix: Option<String>,
54 custom_filters: Vec<Arc<dyn Filter>>,
55}
56
57impl Default for FoxyLoader {
58 fn default() -> Self {
59 Self {
60 config_builder: None,
61 config_file_path: None,
62 use_env_vars: false,
63 env_prefix: None,
64 custom_filters: Vec::new(),
65 }
66 }
67}
68
69impl FoxyLoader {
70 pub fn new() -> Self {
72 Self::default()
73 }
74
75 pub fn with_config(mut self, config: Config) -> Self {
77 self.config_builder = Some(config);
78 self
79 }
80
81 pub fn with_config_file(mut self, file_path: &str) -> Self {
83 self.config_file_path = Some(file_path.to_string());
84 self
85 }
86
87 pub fn with_env_vars(mut self) -> Self {
89 self.use_env_vars = true;
90 self
91 }
92
93 pub fn with_env_prefix(mut self, prefix: &str) -> Self {
95 self.env_prefix = Some(prefix.to_string());
96 self.use_env_vars = true;
97 self
98 }
99
100 pub fn with_provider<P: ConfigProvider + 'static>(self, provider: P) -> Self {
102 let config_builder = match self.config_builder {
103 Some(_) => Config::builder().with_provider(provider),
104 None => Config::builder().with_provider(provider),
105 };
106
107 Self {
108 config_builder: Some(config_builder.build()),
109 ..self
110 }
111 }
112
113 pub fn with_filter<F: Filter + 'static>(mut self, filter: F) -> Self {
115 self.custom_filters.push(Arc::new(filter));
116 self
117 }
118
119 pub async fn build(self) -> Result<Foxy, LoaderError> {
121 let config = if let Some(config) = self.config_builder {
123 config
124 } else {
125 let mut config_builder = Config::builder();
126
127 if self.use_env_vars {
129 let env_provider = match self.env_prefix {
130 Some(prefix) => EnvConfigProvider::new(&prefix),
131 None => EnvConfigProvider::default(),
132 };
133 config_builder = config_builder.with_provider(env_provider);
134 }
135
136 if let Some(file_path) = self.config_file_path {
138 match FileConfigProvider::new(&file_path) {
139 Ok(file_provider) => {
140 config_builder = config_builder.with_provider(file_provider);
141 },
142 Err(e) => {
143 return Err(LoaderError::ConfigError(e));
144 }
145 }
146 }
147
148 config_builder.build()
149 };
150
151 let config_arc = Arc::new(config);
152
153 let mut logging_config: LoggingConfig = config_arc.get("proxy.logging")
155 .unwrap_or(None)
156 .unwrap_or_default();
157
158 let level_str_from_env = env::var("RUST_LOG").ok();
160 let final_level_str = level_str_from_env.as_deref().unwrap_or(&logging_config.level);
161 let final_level_filter = final_level_str.parse::<LevelFilter>().unwrap_or(LevelFilter::Info);
162
163 logging_config.level = final_level_filter.to_string();
166
167 init_with_config(final_level_filter, &logging_config);
169
170 info_fmt!("Loader", "Foxy starting up");
171
172 #[cfg(feature = "opentelemetry")]
173 {
174 if let Ok(Some(otel_config)) = config_arc.get::<crate::opentelemetry::OpenTelemetryConfig>("proxy.opentelemetry") {
175 info_fmt!("Loader", "OpenTelemetry initialized with endpoint: {} and service name: {}",
176 otel_config.endpoint, otel_config.service_name);
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>> = config_arc.get("proxy.global_filters")?;
188
189 if let Some(global_filters) = global_filters_config {
190 for filter_config in global_filters {
191 let filter = FilterFactory::create_filter(
192 &filter_config.type_,
193 filter_config.config.clone(),
194 )?;
195 proxy_core.add_global_filter(filter).await;
196
197 info_fmt!("Loader", "Added global filter: {}", filter_config.type_);
198 }
199 }
200
201 for filter in self.custom_filters {
203 proxy_core.add_global_filter(filter).await;
204 }
205
206 let server_config: ServerConfig = config_arc.get_or_default("server", ServerConfig::default())?;
208
209 let proxy_server = ProxyServer::new(server_config, Arc::new(proxy_core));
211
212 Ok(Foxy {
214 config: config_arc,
215 server: proxy_server,
216 })
217 }
218}
219
220#[derive(Debug, Clone)]
222pub struct Foxy {
223 config: Arc<Config>,
224 server: ProxyServer,
225}
226
227impl Foxy {
228 pub fn loader() -> FoxyLoader {
230 FoxyLoader::new()
231 }
232
233 pub fn config(&self) -> &Config {
235 &self.config
236 }
237
238 pub async fn start(&self) -> Result<(), LoaderError> {
240 self.server.start().await.map_err(LoaderError::ProxyError)
241 }
242}