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}