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        // Build the configuration
120        let config = if let Some(config) = self.config_builder {
121            config
122        } else {
123            let mut config_builder = Config::builder();
124
125            // Add environment variable provider if enabled
126            if self.use_env_vars {
127                let env_provider = match self.env_prefix {
128                    Some(prefix) => EnvConfigProvider::new(&prefix),
129                    None => EnvConfigProvider::default(),
130                };
131                config_builder = config_builder.with_provider(env_provider);
132            }
133
134            // Add file configuration provider if specified
135            if let Some(file_path) = self.config_file_path {
136                match FileConfigProvider::new(&file_path) {
137                    Ok(file_provider) => {
138                        config_builder = config_builder.with_provider(file_provider);
139                    },
140                    Err(e) => {
141                        return Err(LoaderError::ConfigError(e));
142                    }
143                }
144            }
145
146            config_builder.build()
147        };
148
149        let config_arc = Arc::new(config);
150
151        // Initialize OpenTelemetry first, before any other logging
152        #[cfg(feature = "opentelemetry")]
153        {
154            if let Ok(Some(otel_config)) = config_arc.get::<crate::opentelemetry::OpenTelemetryConfig>("proxy.opentelemetry") {
155                if let Err(e) = crate::opentelemetry::init_opentelemetry(&otel_config) {
156                    return Err(LoaderError::Other(format!("OpenTelemetry initialization failed: {}", e)));
157                }
158                // Don't log here as the logger might not be initialized yet
159            }
160        }
161
162        // Then initialize the standard logger
163        if env_logger::try_init().is_ok() {
164            env_logger::Builder::new()
165                .filter_level(LevelFilter::Trace)
166                .try_init()
167                .ok();
168        }
169
170        log::info!("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                log::info!("OpenTelemetry initialized with endpoint: {} and service name: {}", 
176                          otel_config.endpoint, otel_config.service_name);
177            }
178        }
179
180        // Create the router
181        let router = PredicateRouter::new(config_arc.clone()).await?;
182
183        // Create the proxy core
184        let proxy_core = ProxyCore::new(config_arc.clone(), Arc::new(router)).await?;
185        
186        // Register OpenTelemetry filter if configured
187        #[cfg(feature = "opentelemetry")]
188        {
189            if let Ok(Some(otel_config)) = config_arc.get::<crate::opentelemetry::OpenTelemetryConfig>("proxy.opentelemetry") {
190                crate::opentelemetry::register_filter(&proxy_core, &otel_config).await
191                    .map_err(|e| LoaderError::Other(format!("Failed to register OpenTelemetry filter: {}", e)))?;
192                log::info!("Registered OpenTelemetry filter with service name: {}", otel_config.service_name);
193            }
194        }
195
196        // Load global filters from configuration
197        let global_filters_config: Option<Vec<FilterConfig>> = config_arc.get("proxy.global_filters")?;
198
199        if let Some(global_filters) = global_filters_config {
200            for filter_config in global_filters {
201                let filter = FilterFactory::create_filter(
202                    &filter_config.type_,
203                    filter_config.config.clone(),
204                )?;
205                proxy_core.add_global_filter(filter).await;
206
207                log::info!("Added global filter: {}", filter_config.type_);
208            }
209        }
210
211        // Add custom filters
212        for filter in self.custom_filters {
213            proxy_core.add_global_filter(filter).await;
214        }
215
216        // Get server configuration
217        let server_config: ServerConfig = config_arc.get_or_default("server", ServerConfig::default())?;
218
219        // Create the proxy server
220        let proxy_server = ProxyServer::new(server_config, Arc::new(proxy_core));
221
222        // Create the Foxy instance
223        Ok(Foxy {
224            config: config_arc,
225            server: proxy_server,
226        })
227    }
228}
229
230/// Main Foxy struct that holds the initialized proxy.
231#[derive(Debug, Clone)]
232pub struct Foxy {
233    config: Arc<Config>,
234    server: ProxyServer,
235}
236
237impl Foxy {
238    /// Create a new loader for initializing Foxy.
239    pub fn loader() -> FoxyLoader {
240        FoxyLoader::new()
241    }
242
243    /// Get the configuration.
244    pub fn config(&self) -> &Config {
245        &self.config
246    }
247
248    /// Start the proxy server.
249    pub async fn start(&self) -> Result<(), LoaderError> {
250        self.server.start().await.map_err(LoaderError::ProxyError)
251    }
252}