foxy-io 0.0.1

A configuration-driven and hyper-extensible HTTP proxy library
Documentation
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

//! Loader module for Foxy.
//!
//! This module provides the entry point for initializing and configuring
//! the Foxy proxy library. It allows users to start Foxy with default settings
//! or customize it by providing their own configuration.

use std::sync::Arc;
use thiserror::Error;

use crate::config::{Config, ConfigError, ConfigProvider, EnvConfigProvider, FileConfigProvider};
use crate::{PathRouter, ProxyError, ProxyServer, ServerConfig};
use crate::core::ProxyCore;

/// Errors that can occur during Foxy initialization.
#[derive(Error, Debug)]
pub enum LoaderError {
    /// Configuration error
    #[error("configuration error: {0}")]
    ConfigError(#[from] ConfigError),

    /// Proxy error
    #[error("proxy error: {0}")]
    ProxyError(#[from] ProxyError),

    /// IO error
    #[error("IO error: {0}")]
    IoError(#[from] std::io::Error),

    /// Generic error
    #[error("{0}")]
    Other(String),
}

/// Builder for initializing and configuring Foxy.
#[derive(Debug)]
pub struct FoxyLoader {
    config_builder: Option<Config>,
    config_file_path: Option<String>,
    use_env_vars: bool,
    env_prefix: Option<String>,
}

impl Default for FoxyLoader {
    fn default() -> Self {
        Self {
            config_builder: None,
            config_file_path: None,
            use_env_vars: false,
            env_prefix: None,
        }
    }
}

impl FoxyLoader {
    /// Create a new Foxy loader with default settings.
    pub fn new() -> Self {
        Self::default()
    }

    /// Set a custom configuration to use.
    pub fn with_config(mut self, config: Config) -> Self {
        self.config_builder = Some(config);
        self
    }

    /// Set a configuration file to load.
    pub fn with_config_file(mut self, file_path: &str) -> Self {
        self.config_file_path = Some(file_path.to_string());
        self
    }

    /// Enable environment variable configuration.
    pub fn with_env_vars(mut self) -> Self {
        self.use_env_vars = true;
        self
    }

    /// Set a custom prefix for environment variables (default is "FOXY_").
    pub fn with_env_prefix(mut self, prefix: &str) -> Self {
        self.env_prefix = Some(prefix.to_string());
        self.use_env_vars = true;
        self
    }

    /// Add a custom configuration provider.
    pub fn with_provider<P: ConfigProvider + 'static>(self, provider: P) -> Self {
        let config_builder = match self.config_builder {
            Some(_) => Config::builder().with_provider(provider),
            None => Config::builder().with_provider(provider),
        };

        Self {
            config_builder: Some(config_builder.build()),
            ..self
        }
    }

    /// Build and initialize Foxy.
    pub async fn build(self) -> Result<Foxy, LoaderError> {
        // Build the configuration
        let config = if let Some(config) = self.config_builder {
            config
        } else {
            let mut config_builder = Config::builder();

            // Add environment variable provider if enabled
            if self.use_env_vars {
                let env_provider = match self.env_prefix {
                    Some(prefix) => EnvConfigProvider::new(&prefix),
                    None => EnvConfigProvider::default(),
                };
                config_builder = config_builder.with_provider(env_provider);
            }

            // Add file configuration provider if specified
            if let Some(file_path) = self.config_file_path {
                match FileConfigProvider::new(&file_path) {
                    Ok(file_provider) => {
                        config_builder = config_builder.with_provider(file_provider);
                    },
                    Err(e) => {
                        return Err(LoaderError::ConfigError(e));
                    }
                }
            }

            config_builder.build()
        };

        let config_arc = Arc::new(config);

        // Create the router
        let router = PathRouter::new(config_arc.clone()).await?;

        // Create the proxy core
        let proxy_core = ProxyCore::new(config_arc.clone(), Arc::new(router))?;

        // (existing code for filters...)

        // Get server configuration
        let server_config: ServerConfig = config_arc.get_or_default("server", ServerConfig::default())?;

        // Create the proxy server
        let proxy_server = ProxyServer::new(server_config, Arc::new(proxy_core));

        // Create the Foxy instance
        Ok(Foxy {
            config: config_arc,
            server: proxy_server,
        })
    }
}

/// Main Foxy struct that holds the initialized proxy.
#[derive(Debug, Clone)]
pub struct Foxy {
    config: Arc<Config>,
    server: ProxyServer,
}

impl Foxy {
    /// Create a new loader for initializing Foxy.
    pub fn loader() -> FoxyLoader {
        FoxyLoader::new()
    }

    /// Get the configuration.
    pub fn config(&self) -> &Config {
        &self.config
    }

    /// Start the proxy server.
    pub async fn start(&self) -> Result<(), LoaderError> {
        self.server.start().await.map_err(LoaderError::ProxyError)
    }
}