serverless-fn 0.1.0

A Rust library for simplifying serverless function development and invocation
Documentation
//! Configuration management module.
//!
//! Handles runtime configuration via environment variables and feature flags.

use std::time::Duration;

/// Configuration for serverless function runtime.
#[derive(Debug, Clone)]
pub struct Config {
    /// Base URL for remote service calls.
    pub base_url: String,

    /// Request timeout duration.
    pub timeout: Duration,

    /// Number of retries for failed requests.
    pub retries: u32,

    /// Log level for the runtime.
    pub log_level: String,

    /// Deployment strategy.
    pub deploy_strategy: DeployStrategy,
}

/// Deployment strategy options.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DeployStrategy {
    /// Call function remotely.
    Remote,

    /// Call function locally.
    Local,

    /// Mock server mode for testing.
    Mock,
}

/// Builder for creating Config instances.
///
/// # Examples
///
/// ```rust
/// use serverless_fn::config::{Config, DeployStrategy};
/// use std::time::Duration;
///
/// let config = Config::builder()
///     .base_url("http://localhost:8080")
///     .timeout(Duration::from_secs(60))
///     .retries(5)
///     .log_level("debug")
///     .deploy_strategy(DeployStrategy::Remote)
///     .build();
/// ```
#[derive(Debug, Clone, Default)]
pub struct ConfigBuilder {
    base_url: Option<String>,
    timeout: Option<Duration>,
    retries: Option<u32>,
    log_level: Option<String>,
    deploy_strategy: Option<DeployStrategy>,
}

impl ConfigBuilder {
    /// Creates a new ConfigBuilder with default values.
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Sets the base URL for remote service calls.
    #[must_use]
    pub fn base_url(mut self, base_url: impl Into<String>) -> Self {
        self.base_url = Some(base_url.into());
        self
    }

    /// Sets the request timeout duration.
    #[must_use]
    pub fn timeout(mut self, timeout: Duration) -> Self {
        self.timeout = Some(timeout);
        self
    }

    /// Sets the number of retries for failed requests.
    #[must_use]
    pub fn retries(mut self, retries: u32) -> Self {
        self.retries = Some(retries);
        self
    }

    /// Sets the log level for the runtime.
    #[must_use]
    pub fn log_level(mut self, log_level: impl Into<String>) -> Self {
        self.log_level = Some(log_level.into());
        self
    }

    /// Sets the deployment strategy.
    #[must_use]
    pub fn deploy_strategy(mut self, strategy: DeployStrategy) -> Self {
        self.deploy_strategy = Some(strategy);
        self
    }

    /// Builds the Config from the builder.
    #[must_use]
    pub fn build(self) -> Config {
        Config {
            base_url: self.base_url.unwrap_or_else(|| {
                std::env::var("SERVERLESS_BASE_URL")
                    .unwrap_or_else(|_| "http://localhost:3000".to_string())
            }),
            timeout: self.timeout.unwrap_or_else(|| {
                Duration::from_millis(
                    std::env::var("SERVERLESS_TIMEOUT")
                        .unwrap_or_else(|_| "30000".to_string())
                        .parse()
                        .unwrap_or(30_000),
                )
            }),
            retries: self.retries.unwrap_or_else(|| {
                std::env::var("SERVERLESS_RETRIES")
                    .unwrap_or_else(|_| "3".to_string())
                    .parse()
                    .unwrap_or(3)
            }),
            log_level: self.log_level.unwrap_or_else(|| {
                std::env::var("SERVERLESS_LOG_LEVEL").unwrap_or_else(|_| "info".to_string())
            }),
            deploy_strategy: self
                .deploy_strategy
                .unwrap_or_else(Config::determine_deploy_strategy),
        }
    }
}

impl Config {
    /// Creates a new ConfigBuilder for fluent configuration.
    #[must_use]
    pub fn builder() -> ConfigBuilder {
        ConfigBuilder::new()
    }

    /// Creates a new configuration from environment variables.
    ///
    /// # Environment Variables
    ///
    /// - `SERVERLESS_BASE_URL`: Base URL for remote calls (default: `http://localhost:3000`)
    /// - `SERVERLESS_TIMEOUT`: Request timeout in milliseconds (default: `30000`)
    /// - `SERVERLESS_RETRIES`: Number of retries (default: `3`)
    /// - `SERVERLESS_LOG_LEVEL`: Log level (default: `info`)
    #[must_use]
    pub fn from_env() -> Self {
        let deploy_strategy = Self::determine_deploy_strategy();

        Self {
            base_url: std::env::var("SERVERLESS_BASE_URL")
                .unwrap_or_else(|_| "http://localhost:3000".to_string()),
            timeout: Duration::from_millis(
                std::env::var("SERVERLESS_TIMEOUT")
                    .unwrap_or_else(|_| "30000".to_string())
                    .parse()
                    .unwrap_or(30_000),
            ),
            retries: std::env::var("SERVERLESS_RETRIES")
                .unwrap_or_else(|_| "3".to_string())
                .parse()
                .unwrap_or(3),
            log_level: std::env::var("SERVERLESS_LOG_LEVEL").unwrap_or_else(|_| "info".to_string()),
            deploy_strategy,
        }
    }

    /// Determines the deployment strategy based on feature flags.
    fn determine_deploy_strategy() -> DeployStrategy {
        if cfg!(feature = "mock_server") {
            DeployStrategy::Mock
        } else if cfg!(feature = "local_call") {
            DeployStrategy::Local
        } else {
            DeployStrategy::Remote
        }
    }

    /// Returns the current deployment strategy.
    #[must_use]
    pub fn deploy_strategy(&self) -> &DeployStrategy {
        &self.deploy_strategy
    }

    /// Returns the base URL.
    #[must_use]
    pub fn base_url(&self) -> &str {
        &self.base_url
    }

    /// Returns the timeout duration.
    #[must_use]
    pub fn timeout(&self) -> Duration {
        self.timeout
    }

    /// Returns the number of retries.
    #[must_use]
    pub fn retries(&self) -> u32 {
        self.retries
    }

    /// Returns the log level.
    #[must_use]
    pub fn log_level(&self) -> &str {
        &self.log_level
    }

    /// Initializes logging based on this configuration.
    ///
    /// # Panics
    ///
    /// Panics if the logging subscriber cannot be initialized.
    pub fn init_logging(&self) {
        use tracing_subscriber::{EnvFilter, fmt};

        let env_filter =
            EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(&self.log_level));

        fmt().with_env_filter(env_filter).init();
    }
}

impl Default for Config {
    fn default() -> Self {
        Self::from_env()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_config_default() {
        let config = Config::default();
        assert_eq!(config.base_url, "http://localhost:3000");
        assert_eq!(config.timeout, Duration::from_millis(30_000));
        assert_eq!(config.retries, 3);
        assert_eq!(config.log_level, "info");
    }

    #[test]
    fn test_deploy_strategy_remote() {
        // When no special features are enabled, should be Remote
        if !cfg!(feature = "mock_server") && !cfg!(feature = "local_call") {
            let config = Config::default();
            assert_eq!(*config.deploy_strategy(), DeployStrategy::Remote);
        }
    }

    #[test]
    fn test_config_builder() {
        let config = Config::builder()
            .base_url("http://localhost:8080")
            .timeout(Duration::from_secs(60))
            .retries(5)
            .log_level("debug")
            .build();

        assert_eq!(config.base_url, "http://localhost:8080");
        assert_eq!(config.timeout, Duration::from_secs(60));
        assert_eq!(config.retries, 5);
        assert_eq!(config.log_level, "debug");
    }
}