Skip to main content

serverless_fn/
config.rs

1//! Configuration management module.
2//!
3//! Handles runtime configuration via environment variables and feature flags.
4
5use std::time::Duration;
6
7/// Configuration for serverless function runtime.
8#[derive(Debug, Clone)]
9pub struct Config {
10    /// Base URL for remote service calls.
11    pub base_url: String,
12
13    /// Request timeout duration.
14    pub timeout: Duration,
15
16    /// Number of retries for failed requests.
17    pub retries: u32,
18
19    /// Log level for the runtime.
20    pub log_level: String,
21
22    /// Deployment strategy.
23    pub deploy_strategy: DeployStrategy,
24}
25
26/// Deployment strategy options.
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum DeployStrategy {
29    /// Call function remotely.
30    Remote,
31
32    /// Call function locally.
33    Local,
34
35    /// Mock server mode for testing.
36    Mock,
37}
38
39/// Builder for creating Config instances.
40///
41/// # Examples
42///
43/// ```rust
44/// use serverless_fn::config::{Config, DeployStrategy};
45/// use std::time::Duration;
46///
47/// let config = Config::builder()
48///     .base_url("http://localhost:8080")
49///     .timeout(Duration::from_secs(60))
50///     .retries(5)
51///     .log_level("debug")
52///     .deploy_strategy(DeployStrategy::Remote)
53///     .build();
54/// ```
55#[derive(Debug, Clone, Default)]
56pub struct ConfigBuilder {
57    base_url: Option<String>,
58    timeout: Option<Duration>,
59    retries: Option<u32>,
60    log_level: Option<String>,
61    deploy_strategy: Option<DeployStrategy>,
62}
63
64impl ConfigBuilder {
65    /// Creates a new ConfigBuilder with default values.
66    #[must_use]
67    pub fn new() -> Self {
68        Self::default()
69    }
70
71    /// Sets the base URL for remote service calls.
72    #[must_use]
73    pub fn base_url(mut self, base_url: impl Into<String>) -> Self {
74        self.base_url = Some(base_url.into());
75        self
76    }
77
78    /// Sets the request timeout duration.
79    #[must_use]
80    pub fn timeout(mut self, timeout: Duration) -> Self {
81        self.timeout = Some(timeout);
82        self
83    }
84
85    /// Sets the number of retries for failed requests.
86    #[must_use]
87    pub fn retries(mut self, retries: u32) -> Self {
88        self.retries = Some(retries);
89        self
90    }
91
92    /// Sets the log level for the runtime.
93    #[must_use]
94    pub fn log_level(mut self, log_level: impl Into<String>) -> Self {
95        self.log_level = Some(log_level.into());
96        self
97    }
98
99    /// Sets the deployment strategy.
100    #[must_use]
101    pub fn deploy_strategy(mut self, strategy: DeployStrategy) -> Self {
102        self.deploy_strategy = Some(strategy);
103        self
104    }
105
106    /// Builds the Config from the builder.
107    #[must_use]
108    pub fn build(self) -> Config {
109        Config {
110            base_url: self.base_url.unwrap_or_else(|| {
111                std::env::var("SERVERLESS_BASE_URL")
112                    .unwrap_or_else(|_| "http://localhost:3000".to_string())
113            }),
114            timeout: self.timeout.unwrap_or_else(|| {
115                Duration::from_millis(
116                    std::env::var("SERVERLESS_TIMEOUT")
117                        .unwrap_or_else(|_| "30000".to_string())
118                        .parse()
119                        .unwrap_or(30_000),
120                )
121            }),
122            retries: self.retries.unwrap_or_else(|| {
123                std::env::var("SERVERLESS_RETRIES")
124                    .unwrap_or_else(|_| "3".to_string())
125                    .parse()
126                    .unwrap_or(3)
127            }),
128            log_level: self.log_level.unwrap_or_else(|| {
129                std::env::var("SERVERLESS_LOG_LEVEL").unwrap_or_else(|_| "info".to_string())
130            }),
131            deploy_strategy: self
132                .deploy_strategy
133                .unwrap_or_else(Config::determine_deploy_strategy),
134        }
135    }
136}
137
138impl Config {
139    /// Creates a new ConfigBuilder for fluent configuration.
140    #[must_use]
141    pub fn builder() -> ConfigBuilder {
142        ConfigBuilder::new()
143    }
144
145    /// Creates a new configuration from environment variables.
146    ///
147    /// # Environment Variables
148    ///
149    /// - `SERVERLESS_BASE_URL`: Base URL for remote calls (default: `http://localhost:3000`)
150    /// - `SERVERLESS_TIMEOUT`: Request timeout in milliseconds (default: `30000`)
151    /// - `SERVERLESS_RETRIES`: Number of retries (default: `3`)
152    /// - `SERVERLESS_LOG_LEVEL`: Log level (default: `info`)
153    #[must_use]
154    pub fn from_env() -> Self {
155        let deploy_strategy = Self::determine_deploy_strategy();
156
157        Self {
158            base_url: std::env::var("SERVERLESS_BASE_URL")
159                .unwrap_or_else(|_| "http://localhost:3000".to_string()),
160            timeout: Duration::from_millis(
161                std::env::var("SERVERLESS_TIMEOUT")
162                    .unwrap_or_else(|_| "30000".to_string())
163                    .parse()
164                    .unwrap_or(30_000),
165            ),
166            retries: std::env::var("SERVERLESS_RETRIES")
167                .unwrap_or_else(|_| "3".to_string())
168                .parse()
169                .unwrap_or(3),
170            log_level: std::env::var("SERVERLESS_LOG_LEVEL").unwrap_or_else(|_| "info".to_string()),
171            deploy_strategy,
172        }
173    }
174
175    /// Determines the deployment strategy based on feature flags.
176    fn determine_deploy_strategy() -> DeployStrategy {
177        if cfg!(feature = "mock_server") {
178            DeployStrategy::Mock
179        } else if cfg!(feature = "local_call") {
180            DeployStrategy::Local
181        } else {
182            DeployStrategy::Remote
183        }
184    }
185
186    /// Returns the current deployment strategy.
187    #[must_use]
188    pub fn deploy_strategy(&self) -> &DeployStrategy {
189        &self.deploy_strategy
190    }
191
192    /// Returns the base URL.
193    #[must_use]
194    pub fn base_url(&self) -> &str {
195        &self.base_url
196    }
197
198    /// Returns the timeout duration.
199    #[must_use]
200    pub fn timeout(&self) -> Duration {
201        self.timeout
202    }
203
204    /// Returns the number of retries.
205    #[must_use]
206    pub fn retries(&self) -> u32 {
207        self.retries
208    }
209
210    /// Returns the log level.
211    #[must_use]
212    pub fn log_level(&self) -> &str {
213        &self.log_level
214    }
215
216    /// Initializes logging based on this configuration.
217    ///
218    /// # Panics
219    ///
220    /// Panics if the logging subscriber cannot be initialized.
221    pub fn init_logging(&self) {
222        use tracing_subscriber::{EnvFilter, fmt};
223
224        let env_filter =
225            EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(&self.log_level));
226
227        fmt().with_env_filter(env_filter).init();
228    }
229}
230
231impl Default for Config {
232    fn default() -> Self {
233        Self::from_env()
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240
241    #[test]
242    fn test_config_default() {
243        let config = Config::default();
244        assert_eq!(config.base_url, "http://localhost:3000");
245        assert_eq!(config.timeout, Duration::from_millis(30_000));
246        assert_eq!(config.retries, 3);
247        assert_eq!(config.log_level, "info");
248    }
249
250    #[test]
251    fn test_deploy_strategy_remote() {
252        // When no special features are enabled, should be Remote
253        if !cfg!(feature = "mock_server") && !cfg!(feature = "local_call") {
254            let config = Config::default();
255            assert_eq!(*config.deploy_strategy(), DeployStrategy::Remote);
256        }
257    }
258
259    #[test]
260    fn test_config_builder() {
261        let config = Config::builder()
262            .base_url("http://localhost:8080")
263            .timeout(Duration::from_secs(60))
264            .retries(5)
265            .log_level("debug")
266            .build();
267
268        assert_eq!(config.base_url, "http://localhost:8080");
269        assert_eq!(config.timeout, Duration::from_secs(60));
270        assert_eq!(config.retries, 5);
271        assert_eq!(config.log_level, "debug");
272    }
273}