foxy/loader/
mod.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! High-level entry-point – "turn the key and go".
6//!
7//! The [`FoxyLoader`] consumes configuration, builds the predicate-router,
8//! wires up the filter graph and returns a single [`ProxyCore`] ready to be
9//! passed into [`ProxyServer::serve`].
10
11#[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/// Errors that can occur during Foxy initialization.
26#[derive(Error, Debug)]
27pub enum LoaderError {
28    /// Configuration error
29    #[error("configuration error: {0}")]
30    ConfigError(#[from] ConfigError),
31
32    /// Proxy error
33    #[error("proxy error: {0}")]
34    ProxyError(#[from] ProxyError),
35
36    /// IO error
37    #[error("IO error: {0}")]
38    IoError(#[from] std::io::Error),
39
40    /// Generic error
41    #[error("{0}")]
42    Other(String),
43}
44
45/// Builder for initializing and configuring Foxy.
46#[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    /// Create a new Foxy loader with default settings.
59    pub fn new() -> Self {
60        Self::default()
61    }
62
63    /// Set a custom configuration to use.
64    pub fn with_config(mut self, config: Config) -> Self {
65        self.config_builder = Some(config);
66        self
67    }
68
69    /// Set a configuration file to load.
70    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    /// Enable environment variable configuration.
76    pub fn with_env_vars(mut self) -> Self {
77        self.use_env_vars = true;
78        self
79    }
80
81    /// Set a custom prefix for environment variables (default is "FOXY_").
82    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    /// Add a custom configuration provider.
89    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    /// Add a custom filter.
102    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    /// Build and initialize Foxy.
108    pub async fn build(self) -> Result<Foxy, LoaderError> {
109        // Build the configuration
110        let config = if let Some(config) = self.config_builder {
111            config
112        } else {
113            let mut config_builder = Config::builder();
114
115            // Add environment variable provider if enabled
116            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            // Add file configuration provider if specified
125            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        // Get the full logging config from the file, or use a default if it's missing.
142        let mut logging_config: LoggingConfig = config_arc.get("proxy.logging")
143            .unwrap_or(None)
144            .unwrap_or_default();
145
146        // Determine the final log level, giving precedence to the RUST_LOG environment variable.
147        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        // Update the config object with the final, resolved level.
152        // This ensures to_logger_config() gets the correct string later.
153        logging_config.level = final_level_filter.to_string();
154
155        // Initialize all logging with this single, consistent configuration.
156        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        // Create the router
169        let router = PredicateRouter::new(config_arc.clone()).await?;
170
171        // Create the proxy core
172        let proxy_core = ProxyCore::new(config_arc.clone(), Arc::new(router)).await?;
173
174        // Load global filters from configuration
175        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        // Add custom filters
190        for filter in self.custom_filters {
191            proxy_core.add_global_filter(filter).await;
192        }
193
194        // Get server configuration
195        let server_config: ServerConfig = config_arc.get_or_default("server", ServerConfig::default())?;
196
197        // Create the proxy server
198        let proxy_server = ProxyServer::new(server_config, Arc::new(proxy_core));
199
200        // Create the Foxy instance
201        Ok(Foxy {
202            config: config_arc,
203            server: proxy_server,
204        })
205    }
206}
207
208/// Main Foxy struct that holds the initialized proxy.
209#[derive(Debug, Clone)]
210pub struct Foxy {
211    config: Arc<Config>,
212    server: ProxyServer,
213}
214
215impl Foxy {
216    /// Create a new loader for initializing Foxy.
217    pub fn loader() -> FoxyLoader {
218        FoxyLoader::new()
219    }
220
221    /// Get the configuration.
222    pub fn config(&self) -> &Config {
223        &self.config
224    }
225
226    /// Start the proxy server.
227    pub async fn start(&self) -> Result<(), LoaderError> {
228        self.server.start().await.map_err(LoaderError::ProxyError)
229    }
230}