foxy/config/
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//! Foxy configuration subsystem
6//!
7//! The subsystem is deliberately **pluggable**.  A running proxy is created
8//! from an ordered list of [`ConfigProvider`]s; later providers override
9//! earlier ones.  Typical stacking order looks like this:
10//!
11//! 1. `FileConfigProvider` – `foxy.{toml,json,yaml}`
12//! 2. `EnvConfigProvider`  – `FOXY__ROUTES__0__TARGET=https://…`
13//! 3. *your* provider implementing [`ConfigProvider`]
14//!
15//! Calling [`Config::get`] is therefore *deterministic*: the first provider
16//! in the chain that yields a key wins.
17//!
18//! The following document describes every *first-class* configuration key.
19//!
20//! | key | type | default | description |
21//! |-----|------|---------|-------------|
22//! | `server.listen`      | `"[::]:8080"` | – | Socket-address to bind       |
23//! | `server.body_limit`  | `5mb`         | – | Maximum inbound body size     |
24//! | `server.header_limit`| `256kb`       | – | Maximum combined header bytes |
25//! | `routes`             | *array*       | – | List of routing rules         |
26//!
27//! See [`README.md`](../../README.md#configuration) for a more narrative guide.
28
29mod file;
30mod env;
31mod proxy;
32pub mod error;
33
34#[cfg(test)]
35mod tests;
36
37pub use error::ConfigError;
38pub use file::FileConfigProvider;
39pub use env::EnvConfigProvider;
40
41use std::fmt::Debug;
42use std::sync::Arc;
43use serde::de::DeserializeOwned;
44use serde_json::Value;
45
46/// Core configuration provider trait that all configuration sources must implement.
47/// This trait is object-safe since it doesn't contain generic methods.
48pub trait ConfigProvider: Debug + Send + Sync {
49    /// Check if the configuration provider has a value for the given key.
50    fn has(&self, key: &str) -> bool;
51
52    /// Get the name of the configuration provider for debugging purposes.
53    fn provider_name(&self) -> &str;
54
55    /// Get a raw configuration value by key.
56    /// Returns a JSON Value that can be later deserialized into specific types.
57    fn get_raw(&self, key: &str) -> Result<Option<Value>, ConfigError>;
58}
59
60/// Extension trait for ConfigProvider that provides methods for typed access.
61/// This trait is not object-safe because it has generic methods.
62pub trait ConfigProviderExt: ConfigProvider {
63    /// Get a configuration value by key and deserialize it to the specified type.
64    fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>, ConfigError> {
65        match self.get_raw(key)? {
66            Some(value) => {
67                serde_json::from_value(value)
68                    .map(Some)
69                    .map_err(|e| ConfigError::ParseError(format!("failed to deserialize '{key}': {e}")))
70            },
71            None => Ok(None),
72        }
73    }
74}
75
76// Implement ConfigProviderExt for any type that implements ConfigProvider
77impl<T: ConfigProvider> ConfigProviderExt for T {}
78
79/// Builder for the configuration system.
80#[derive(Debug, Default)]
81pub struct ConfigBuilder {
82    providers: Vec<Arc<dyn ConfigProvider>>,
83}
84
85impl ConfigBuilder {
86    /// Create a new configuration builder.
87    pub fn new() -> Self {
88        Self::default()
89    }
90
91    /// Add a configuration provider.
92    pub fn with_provider<P: ConfigProvider + 'static>(mut self, provider: P) -> Self {
93        self.providers.push(Arc::new(provider));
94        self
95    }
96
97    /// Build the configuration.
98    pub fn build(self) -> Config {
99        Config {
100            providers: self.providers,
101        }
102    }
103}
104
105/// Main configuration struct that holds all providers and handles retrieving values.
106#[derive(Debug, Clone)]
107pub struct Config {
108    providers: Vec<Arc<dyn ConfigProvider>>,
109}
110
111impl Config {
112    /// Create a new configuration builder.
113    pub fn builder() -> ConfigBuilder {
114        ConfigBuilder::new()
115    }
116
117    /// Get a raw configuration value.
118    fn get_raw(&self, key: &str) -> Result<Option<Value>, ConfigError> {
119        // Iterate through providers in reverse order to respect priority
120        // Later providers (higher index) should override earlier ones
121        for provider in self.providers.iter().rev() {
122            if provider.has(key) {
123                return provider.get_raw(key);
124            }
125        }
126        Ok(None)
127    }
128
129    /// Get a configuration value by key, checking all providers in the order they were added.
130    /// Returns the first value found.
131    pub fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>, ConfigError> {
132        match self.get_raw(key)? {
133            Some(value) => {
134                serde_json::from_value(value)
135                    .map(Some)
136                    .map_err(|e| ConfigError::ParseError(format!("failed to deserialize '{key}': {e}")))
137            },
138            None => Ok(None),
139        }
140    }
141
142    /// Get a configuration value by key with a default fallback value.
143    pub fn get_or_default<T: DeserializeOwned>(&self, key: &str, default: T) -> Result<T, ConfigError> {
144        match self.get(key)? {
145            Some(value) => Ok(value),
146            None => Ok(default),
147        }
148    }
149
150    /// Create a default configuration using the file-based provider.
151    pub fn default_file(file_path: &str) -> Result<Self, ConfigError> {
152        let provider = FileConfigProvider::new(file_path)?;
153        Ok(Self::builder().with_provider(provider).build())
154    }
155}