Skip to main content

systemprompt_models/config/
mod.rs

1//! Global runtime [`Config`] singleton and validation helpers.
2//!
3//! [`Config`] is the resolved, flat configuration installed once at
4//! startup into a process-wide `OnceLock` and read via [`Config::get`].
5//! Submodules cover environment classification, path/postgres-URL
6//! validation, rate-limit shapes, and verbosity levels.
7//! Accessors return [`crate::errors::ConfigError`] when not initialized.
8
9use std::sync::OnceLock;
10use systemprompt_traits::ConfigProvider;
11
12use crate::auth::JwtAudience;
13use crate::profile::{ContentNegotiationConfig, SecurityHeadersConfig};
14
15mod environment;
16mod paths;
17mod rate_limits;
18mod validation;
19mod verbosity;
20
21pub use environment::Environment;
22pub use paths::PathNotConfiguredError;
23pub use rate_limits::RateLimitConfig;
24pub use validation::{
25    format_path_errors, validate_optional_path, validate_postgres_url, validate_profile_paths,
26    validate_required_optional_path, validate_required_path,
27};
28pub use verbosity::VerbosityLevel;
29
30static CONFIG: OnceLock<Config> = OnceLock::new();
31
32#[derive(Debug, Clone)]
33pub struct Config {
34    pub sitename: String,
35    pub database_type: String,
36    pub database_url: String,
37    pub database_write_url: Option<String>,
38    pub github_link: String,
39    pub github_token: Option<String>,
40    pub system_path: String,
41    pub services_path: String,
42    pub bin_path: String,
43    pub skills_path: String,
44    pub settings_path: String,
45    pub content_config_path: String,
46    pub geoip_database_path: Option<String>,
47    pub web_path: String,
48    pub web_config_path: String,
49    pub web_metadata_path: String,
50    pub host: String,
51    pub port: u16,
52    pub api_server_url: String,
53    pub api_internal_url: String,
54    pub api_external_url: String,
55    pub jwt_issuer: String,
56    pub jwt_access_token_expiration: i64,
57    pub jwt_refresh_token_expiration: i64,
58    pub jwt_audiences: Vec<JwtAudience>,
59    pub use_https: bool,
60    pub rate_limits: RateLimitConfig,
61    pub cors_allowed_origins: Vec<String>,
62    pub is_cloud: bool,
63    pub content_negotiation: ContentNegotiationConfig,
64    pub security_headers: SecurityHeadersConfig,
65    pub allow_registration: bool,
66}
67
68impl Config {
69    pub fn is_initialized() -> bool {
70        CONFIG.get().is_some()
71    }
72
73    pub fn get() -> Result<&'static Self, crate::errors::ConfigError> {
74        CONFIG
75            .get()
76            .ok_or(crate::errors::ConfigError::NotInitialized)
77    }
78
79    pub fn install(config: Self) -> Result<(), Box<Self>> {
80        CONFIG.set(config).map_err(Box::new)
81    }
82}
83
84impl ConfigProvider for Config {
85    fn get(&self, key: &str) -> Option<String> {
86        match key {
87            "database_type" => Some(self.database_type.clone()),
88            "database_url" => Some(self.database_url.clone()),
89            "database_write_url" => self.database_write_url.clone(),
90            "host" => Some(self.host.clone()),
91            "port" => Some(self.port.to_string()),
92            "system_path" => Some(self.system_path.clone()),
93            "services_path" => Some(self.services_path.clone()),
94            "bin_path" => Some(self.bin_path.clone()),
95            "skills_path" => Some(self.skills_path.clone()),
96            "settings_path" => Some(self.settings_path.clone()),
97            "content_config_path" => Some(self.content_config_path.clone()),
98            "web_path" => Some(self.web_path.clone()),
99            "web_config_path" => Some(self.web_config_path.clone()),
100            "web_metadata_path" => Some(self.web_metadata_path.clone()),
101            "sitename" => Some(self.sitename.clone()),
102            "github_link" => Some(self.github_link.clone()),
103            "github_token" => self.github_token.clone(),
104            "api_server_url" => Some(self.api_server_url.clone()),
105            "api_external_url" => Some(self.api_external_url.clone()),
106            "jwt_issuer" => Some(self.jwt_issuer.clone()),
107            "is_cloud" => Some(self.is_cloud.to_string()),
108            _ => None,
109        }
110    }
111
112    fn database_url(&self) -> &str {
113        &self.database_url
114    }
115
116    fn database_write_url(&self) -> Option<&str> {
117        self.database_write_url.as_deref()
118    }
119
120    fn system_path(&self) -> &str {
121        &self.system_path
122    }
123
124    fn api_port(&self) -> u16 {
125        self.port
126    }
127
128    fn as_any(&self) -> &dyn std::any::Any {
129        self
130    }
131}