reinhardt_conf/settings.rs
1//! # Settings Module
2//!
3//! Django-inspired settings system for Reinhardt projects.
4//! This module provides configuration management for Reinhardt applications.
5
6// The `From<&FragmentTemplateConfig>` bridge below names the deprecated
7// `TemplateConfig` during the 0.2 compatibility window.
8#![allow(deprecated)]
9
10pub mod builder;
11pub mod cache;
12/// Trait for composed settings structs generated by the `#[settings(...)]` macro.
13pub mod composed;
14pub mod contacts;
15pub mod core_settings;
16pub mod cors;
17pub mod email;
18pub mod env;
19pub mod env_loader;
20pub mod env_parser;
21pub mod fragment;
22pub mod i18n;
23pub mod interpolation;
24pub mod logging;
25pub mod media;
26pub(crate) mod merge;
27/// OpenAPI documentation endpoint configuration.
28pub mod openapi;
29/// Field-level policy types for settings fragments.
30pub mod policy;
31pub mod prelude;
32pub mod profile;
33/// Typed settings schema references and recursive settings metadata.
34pub mod schema;
35pub mod secret_types;
36pub mod security;
37pub mod session;
38pub mod sources;
39pub mod static_files;
40pub mod template_settings;
41pub mod typed_deserializer;
42pub mod validation;
43
44// Dynamic settings (async feature required)
45#[cfg(feature = "async")]
46pub mod dynamic;
47
48#[cfg(feature = "async")]
49pub mod backends;
50
51/// Secret management with provider-based storage and rotation support.
52#[cfg(feature = "async")]
53pub mod secrets;
54
55#[cfg(feature = "encryption")]
56pub mod encryption;
57
58#[cfg(feature = "async")]
59pub mod audit;
60
61#[cfg(feature = "hot-reload")]
62pub mod hot_reload;
63
64/// Additional application-level configuration types.
65pub mod config;
66/// Database connection configuration types and helpers.
67pub mod database_config;
68/// Settings documentation and introspection utilities.
69pub mod docs;
70/// Test utilities for settings configuration.
71pub mod testing;
72
73use serde::{Deserialize, Serialize};
74use std::collections::HashMap;
75use std::path::PathBuf;
76
77/// Contact information for administrators and managers
78///
79/// Used for error notifications, broken link notifications, etc.
80///
81/// # Examples
82///
83/// ```
84/// use reinhardt_conf::settings::Contact;
85///
86/// let admin = Contact::new("John Doe", "john@example.com");
87/// assert_eq!(admin.name, "John Doe");
88/// assert_eq!(admin.email, "john@example.com");
89/// ```
90#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
91pub struct Contact {
92 /// Person's name
93 pub name: String,
94 /// Email address
95 pub email: String,
96}
97
98impl Contact {
99 /// Create a new contact
100 ///
101 /// # Examples
102 ///
103 /// ```
104 /// use reinhardt_conf::settings::Contact;
105 ///
106 /// let contact = Contact::new("Alice Smith", "alice@example.com");
107 /// assert_eq!(contact.name, "Alice Smith");
108 /// assert_eq!(contact.email, "alice@example.com");
109 /// ```
110 pub fn new(name: impl Into<String>, email: impl Into<String>) -> Self {
111 Self {
112 name: name.into(),
113 email: email.into(),
114 }
115 }
116}
117
118// Re-export DatabaseConfig from database_config module
119pub use database_config::DatabaseConfig;
120
121// Re-export policy types
122pub use policy::{FieldPolicy, FieldRequirement};
123
124// Re-export ComposedSettings trait
125pub use composed::ComposedSettings;
126
127// Re-export the merge strategy selector for SettingsBuilder. See issue #4260.
128pub use builder::MergeStrategy;
129
130/// Template engine configuration
131///
132/// Deprecated in favor of the [`TemplateSettings`](template_settings::TemplateSettings)
133/// fragment (and its nested [`FragmentTemplateConfig`](template_settings::FragmentTemplateConfig)
134/// value object), which compose with the `#[settings]` macro. A
135/// [`From<&FragmentTemplateConfig>`] bridge is provided for migration.
136#[deprecated(
137 since = "0.2.0",
138 note = "Use `TemplateSettings` with the `#[settings]` macro instead."
139)]
140#[non_exhaustive]
141#[derive(Clone, Debug, Serialize, Deserialize)]
142pub struct TemplateConfig {
143 /// Template backend/engine
144 pub backend: String,
145
146 /// Directories to search for templates
147 pub dirs: Vec<PathBuf>,
148
149 /// Search for templates in app directories
150 pub app_dirs: bool,
151
152 /// Template engine options
153 pub options: HashMap<String, serde_json::Value>,
154}
155
156impl TemplateConfig {
157 /// Create a new template configuration
158 ///
159 /// # Examples
160 ///
161 /// ```
162 /// use reinhardt_conf::settings::TemplateConfig;
163 ///
164 /// let config = TemplateConfig::new("reinhardt.template.backends.jinja2.Jinja2");
165 ///
166 /// assert_eq!(config.backend, "reinhardt.template.backends.jinja2.Jinja2");
167 /// assert!(config.app_dirs);
168 /// assert_eq!(config.dirs.len(), 0);
169 /// ```
170 pub fn new(backend: impl Into<String>) -> Self {
171 Self {
172 backend: backend.into(),
173 dirs: vec![],
174 app_dirs: true,
175 options: HashMap::new(),
176 }
177 }
178 /// Add a template directory
179 ///
180 /// # Examples
181 ///
182 /// ```
183 /// use reinhardt_conf::settings::TemplateConfig;
184 /// use std::path::PathBuf;
185 ///
186 /// let config = TemplateConfig::new("reinhardt.template.backends.jinja2.Jinja2")
187 /// .add_dir("/app/templates");
188 ///
189 /// assert_eq!(config.dirs.len(), 1);
190 /// assert_eq!(config.dirs[0], PathBuf::from("/app/templates"));
191 /// ```
192 pub fn add_dir(mut self, dir: impl Into<PathBuf>) -> Self {
193 self.dirs.push(dir.into());
194 self
195 }
196}
197
198impl Default for TemplateConfig {
199 fn default() -> Self {
200 let mut options = HashMap::new();
201 options.insert(
202 "context_processors".to_string(),
203 serde_json::json!([
204 "reinhardt.template.context_processors.request",
205 "reinhardt.contrib.auth.context_processors.auth",
206 "reinhardt.contrib.messages.context_processors.messages",
207 ]),
208 );
209
210 Self {
211 backend: "reinhardt.template.backends.jinja2.Jinja2".to_string(),
212 dirs: vec![],
213 app_dirs: true,
214 options,
215 }
216 }
217}
218
219impl From<&template_settings::FragmentTemplateConfig> for TemplateConfig {
220 /// Bridge a settings-fragment template config into the deprecated
221 /// compatibility [`TemplateConfig`] during the 0.2 migration window.
222 fn from(settings: &template_settings::FragmentTemplateConfig) -> Self {
223 Self {
224 backend: settings.backend.clone(),
225 dirs: settings.dirs.clone(),
226 app_dirs: settings.app_dirs,
227 options: settings.options.clone(),
228 }
229 }
230}
231
232/// Build the deprecated [`TemplateConfig`] list from a
233/// [`TemplateSettings`](template_settings::TemplateSettings) fragment.
234///
235/// Prefer the [`TemplateSettings`](template_settings::TemplateSettings) fragment
236/// directly in new code; this helper exists to ease migration off the legacy
237/// `TemplateConfig` type.
238pub fn create_template_configs_from_settings(
239 settings: &template_settings::TemplateSettings,
240) -> Vec<TemplateConfig> {
241 settings.configs.iter().map(TemplateConfig::from).collect()
242}
243
244/// Middleware configuration
245#[non_exhaustive]
246#[derive(Clone, Debug, Serialize, Deserialize)]
247pub struct MiddlewareConfig {
248 /// Full path to the middleware class
249 pub path: String,
250
251 /// Middleware options
252 pub options: HashMap<String, serde_json::Value>,
253}
254
255impl MiddlewareConfig {
256 /// Create a new middleware configuration
257 ///
258 /// # Examples
259 ///
260 /// ```
261 /// use reinhardt_conf::settings::MiddlewareConfig;
262 ///
263 /// let middleware = MiddlewareConfig::new("myapp.middleware.CustomMiddleware");
264 ///
265 /// assert_eq!(middleware.path, "myapp.middleware.CustomMiddleware");
266 /// assert_eq!(middleware.options.len(), 0);
267 /// ```
268 pub fn new(path: impl Into<String>) -> Self {
269 Self {
270 path: path.into(),
271 options: HashMap::new(),
272 }
273 }
274 /// Add an option to the middleware
275 ///
276 /// # Examples
277 ///
278 /// ```
279 /// use reinhardt_conf::settings::MiddlewareConfig;
280 ///
281 /// let middleware = MiddlewareConfig::new("myapp.middleware.CustomMiddleware")
282 /// .with_option("timeout", serde_json::json!(30));
283 ///
284 /// assert_eq!(middleware.options.get("timeout"), Some(&serde_json::json!(30)));
285 /// ```
286 pub fn with_option(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
287 self.options.insert(key.into(), value);
288 self
289 }
290}