Skip to main content

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}