Skip to main content

karbon_framework/config/
config.rs

1use std::env;
2
3/// Application configuration loaded from environment variables
4#[derive(Debug, Clone)]
5pub struct Config {
6    // Server
7    pub port: u16,
8    pub environment: String,
9    pub log_level: String,
10
11    // Database
12    pub db_host: String,
13    pub db_port: u16,
14    pub db_name: String,
15    pub db_user: String,
16    pub db_password: String,
17    pub db_max_connections: u32,
18
19    // JWT
20    pub jwt_secret: String,
21    pub jwt_expiration: i64,           // access token TTL in seconds
22    pub refresh_token_expiration: i64, // refresh token TTL in seconds
23
24    // CORS
25    pub cors_origins: Vec<String>,
26
27    // Upload
28    pub upload_dir: String,
29    pub upload_max_size: u64, // bytes
30
31    // CDN
32    pub cdn_url: String,
33
34    // Mail
35    pub mail_from: String,
36    pub smtp_host: String,
37    pub smtp_port: u16,
38    pub smtp_user: String,
39    pub smtp_password: String,
40
41    // Site
42    pub site_name: String,
43    pub site_url: String,
44    pub base_url: String,
45}
46
47impl Config {
48    /// Load configuration from environment variables
49    pub fn from_env() -> Self {
50        Self {
51            // Server
52            port: env_parse("PORT", 3000),
53            environment: env_or("APP_ENV", "development"),
54            log_level: env_or("LOG_LEVEL", "info"),
55
56            // Database
57            db_host: env_or("DB_HOST", "127.0.0.1"),
58            #[cfg(feature = "mysql")]
59            db_port: env_parse("DB_PORT", 3306),
60            #[cfg(feature = "postgres")]
61            db_port: env_parse("DB_PORT", 5432),
62            db_name: env_required("DB_NAME"),
63            db_user: env_required("DB_USER"),
64            db_password: env_or("DB_PASSWORD", ""),
65            db_max_connections: env_parse("DB_MAX_CONNECTIONS", 10),
66
67            // JWT
68            jwt_secret: env_required("JWT_SECRET"),
69            jwt_expiration: env_parse("JWT_EXPIRATION", 900),              // 15min
70            refresh_token_expiration: env_parse("REFRESH_TOKEN_EXPIRATION", 2_592_000), // 30 days
71
72            // CORS
73            cors_origins: env_or("CORS_ORIGINS", "*")
74                .split(',')
75                .map(|s| s.trim().to_string())
76                .collect(),
77
78            // Upload
79            upload_dir: env_or("UPLOAD_DIR", "./uploads"),
80            upload_max_size: env_parse("UPLOAD_MAX_SIZE", 10_485_760), // 10MB
81
82            // CDN
83            cdn_url: env_or("CDN_URL", ""),
84
85            // Mail
86            mail_from: env_or("MAIL_FROM", "noreply@localhost"),
87            smtp_host: env_or("SMTP_HOST", "localhost"),
88            smtp_port: env_parse("SMTP_PORT", 587),
89            smtp_user: env_or("SMTP_USER", ""),
90            smtp_password: env_or("SMTP_PASSWORD", ""),
91
92            // Site
93            site_name: env_or("SITE_NAME", "Karbon"),
94            site_url: env_or("SITE_URL", "http://localhost:3000"),
95            base_url: env_or("BASE_URL", "http://localhost:3000/"),
96        }
97    }
98
99    /// Create a minimal config for unit tests (no env vars needed)
100    pub fn test_config(jwt_secret: &str) -> Self {
101        Self {
102            port: 3005,
103            environment: "test".into(),
104            log_level: "error".into(),
105            db_host: "127.0.0.1".into(),
106            db_port: 3306,
107            db_name: "test".into(),
108            db_user: "test".into(),
109            db_password: "test".into(),
110            db_max_connections: 1,
111            jwt_secret: jwt_secret.into(),
112            jwt_expiration: 3600,
113            refresh_token_expiration: 86400,
114            cors_origins: vec!["*".into()],
115            upload_dir: "/tmp".into(),
116            upload_max_size: 10_485_760,
117            cdn_url: "".into(),
118            mail_from: "test@test.com".into(),
119            smtp_host: "localhost".into(),
120            smtp_port: 587,
121            smtp_user: "".into(),
122            smtp_password: "".into(),
123            site_name: "Test".into(),
124            site_url: "http://localhost:3005".into(),
125            base_url: "http://localhost:3005/".into(),
126        }
127    }
128
129    /// Check if running in production
130    pub fn is_production(&self) -> bool {
131        self.environment == "production"
132    }
133
134    /// Get full database URL
135    #[cfg(feature = "mysql")]
136    pub fn database_url(&self) -> String {
137        format!(
138            "mysql://{}:{}@{}:{}/{}",
139            self.db_user, self.db_password, self.db_host, self.db_port, self.db_name
140        )
141    }
142
143    /// Get full database URL
144    #[cfg(feature = "postgres")]
145    pub fn database_url(&self) -> String {
146        format!(
147            "postgresql://{}:{}@{}:{}/{}",
148            self.db_user, self.db_password, self.db_host, self.db_port, self.db_name
149        )
150    }
151}
152
153/// Get an env var or return a default value
154fn env_or(key: &str, default: &str) -> String {
155    env::var(key).unwrap_or_else(|_| default.to_string())
156}
157
158/// Get a required env var, panic if missing
159fn env_required(key: &str) -> String {
160    env::var(key).unwrap_or_else(|_| panic!("Environment variable {} is required", key))
161}
162
163/// Parse an env var into a type, with a default
164fn env_parse<T: std::str::FromStr>(key: &str, default: T) -> T {
165    env::var(key)
166        .ok()
167        .and_then(|v| v.parse().ok())
168        .unwrap_or(default)
169}