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::collections::HashMap;
15use std::sync::Arc;
16use log::LevelFilter;
17use serde_json::Value;
18use thiserror::Error;
19
20use crate::config::{Config, ConfigError, ConfigProvider, EnvConfigProvider, FileConfigProvider};
21use crate::router::{FilterConfig, PredicateRouter, RouteConfig};
22use crate::{Filter, FilterFactory, ProxyError, ProxyServer, ServerConfig};
23use crate::core::ProxyCore;
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)]
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
55impl Default for FoxyLoader {
56    fn default() -> Self {
57        Self {
58            config_builder: None,
59            config_file_path: None,
60            use_env_vars: false,
61            env_prefix: None,
62            custom_filters: Vec::new(),
63        }
64    }
65}
66
67impl FoxyLoader {
68    /// Create a new Foxy loader with default settings.
69    pub fn new() -> Self {
70        Self::default()
71    }
72
73    /// Set a custom configuration to use.
74    pub fn with_config(mut self, config: Config) -> Self {
75        self.config_builder = Some(config);
76        self
77    }
78
79    /// Set a configuration file to load.
80    pub fn with_config_file(mut self, file_path: &str) -> Self {
81        self.config_file_path = Some(file_path.to_string());
82        self
83    }
84
85    /// Enable environment variable configuration.
86    pub fn with_env_vars(mut self) -> Self {
87        self.use_env_vars = true;
88        self
89    }
90
91    /// Set a custom prefix for environment variables (default is "FOXY_").
92    pub fn with_env_prefix(mut self, prefix: &str) -> Self {
93        self.env_prefix = Some(prefix.to_string());
94        self.use_env_vars = true;
95        self
96    }
97
98    /// Add a custom configuration provider.
99    pub fn with_provider<P: ConfigProvider + 'static>(self, provider: P) -> Self {
100        let config_builder = match self.config_builder {
101            Some(_) => Config::builder().with_provider(provider),
102            None => Config::builder().with_provider(provider),
103        };
104
105        Self {
106            config_builder: Some(config_builder.build()),
107            ..self
108        }
109    }
110
111    /// Add a custom filter.
112    pub fn with_filter<F: Filter + 'static>(mut self, filter: F) -> Self {
113        self.custom_filters.push(Arc::new(filter));
114        self
115    }
116
117    /// Build and initialize Foxy.
118    pub async fn build(self) -> Result<Foxy, LoaderError> {
119        // Only initialize the logger if it hasn't been initialized yet
120        // This helps prevent issues in tests that run multiple builds
121        if env_logger::try_init().is_ok() {
122            env_logger::Builder::new()
123                .filter_level(LevelFilter::Trace)  // Set this to Debug or Trace for more detailed logs
124                .try_init()
125                .ok(); // Ignore errors if logger is already initialized
126        }
127
128        log::info!("Foxy starting up");
129
130        // Build the configuration
131        let config = if let Some(config) = self.config_builder {
132            config
133        } else {
134            let mut config_builder = Config::builder();
135
136            // Add environment variable provider if enabled
137            if self.use_env_vars {
138                let env_provider = match self.env_prefix {
139                    Some(prefix) => EnvConfigProvider::new(&prefix),
140                    None => EnvConfigProvider::default(),
141                };
142                config_builder = config_builder.with_provider(env_provider);
143            }
144
145            // Add file configuration provider if specified
146            if let Some(file_path) = self.config_file_path {
147                match FileConfigProvider::new(&file_path) {
148                    Ok(file_provider) => {
149                        config_builder = config_builder.with_provider(file_provider);
150                    },
151                    Err(e) => {
152                        return Err(LoaderError::ConfigError(e));
153                    }
154                }
155            }
156
157            config_builder.build()
158        };
159
160        let config_arc = Arc::new(config);
161
162        // Create the router
163        let router = PredicateRouter::new(config_arc.clone()).await?;
164
165        // Create the proxy core
166        let proxy_core = ProxyCore::new(config_arc.clone(), Arc::new(router)).await?;
167
168        // Load global filters from configuration
169        let global_filters_config: Option<Vec<FilterConfig>> = config_arc.get("proxy.global_filters")?;
170
171        if let Some(global_filters) = global_filters_config {
172            for filter_config in global_filters {
173                let filter = FilterFactory::create_filter(
174                    &filter_config.type_,
175                    filter_config.config.clone(),
176                )?;
177                proxy_core.add_global_filter(filter).await;
178
179                log::info!("Added global filter: {}", filter_config.type_);
180            }
181        }
182
183        // Add custom filters
184        for filter in self.custom_filters {
185            proxy_core.add_global_filter(filter).await;
186        }
187
188        // Get server configuration
189        let server_config: ServerConfig = config_arc.get_or_default("server", ServerConfig::default())?;
190
191        // Create the proxy server
192        let proxy_server = ProxyServer::new(server_config, Arc::new(proxy_core));
193
194        // Create the Foxy instance
195        Ok(Foxy {
196            config: config_arc,
197            server: proxy_server,
198        })
199    }
200}
201
202/// Main Foxy struct that holds the initialized proxy.
203#[derive(Debug, Clone)]
204pub struct Foxy {
205    config: Arc<Config>,
206    server: ProxyServer,
207}
208
209impl Foxy {
210    /// Create a new loader for initializing Foxy.
211    pub fn loader() -> FoxyLoader {
212        FoxyLoader::new()
213    }
214
215    /// Get the configuration.
216    pub fn config(&self) -> &Config {
217        &self.config
218    }
219
220    /// Start the proxy server.
221    pub async fn start(&self) -> Result<(), LoaderError> {
222        self.server.start().await.map_err(LoaderError::ProxyError)
223    }
224}