1use crate::core::prelude::*;
6use serde::{Deserialize, Serialize};
7use std::path::PathBuf;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11pub enum ServerMode {
12 Dev, Prod, }
15
16impl Default for ServerMode {
17 fn default() -> Self {
18 Self::Dev
19 }
20}
21
22impl std::fmt::Display for ServerMode {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 match self {
25 Self::Dev => write!(f, "development"),
26 Self::Prod => write!(f, "production"),
27 }
28 }
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct ServerConfig {
34 pub mode: ServerMode,
35 pub port: u16,
36 pub host: String,
37 pub static_dir: PathBuf,
38 pub hot_reload: bool,
39 pub cors_enabled: bool,
40 pub debug_logs: bool,
41
42 pub dev: DevConfig,
44
45 pub prod: ProdConfig,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct DevConfig {
51 pub auto_compile_scss: bool,
52 pub watch_files: Vec<String>,
53 pub reload_delay_ms: u64,
54 pub show_debug_routes: bool,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct ProdConfig {
59 pub enable_tls: bool,
60 pub cert_path: Option<PathBuf>,
61 pub key_path: Option<PathBuf>,
62 pub compression: bool,
63 pub cache_static_files: bool,
64}
65
66impl Default for ServerConfig {
67 fn default() -> Self {
68 Self {
69 mode: ServerMode::Dev,
70 port: 8080,
71 host: "127.0.0.1".to_string(),
72 static_dir: PathBuf::from("static"),
73 hot_reload: true,
74 cors_enabled: true,
75 debug_logs: true,
76
77 dev: DevConfig {
78 auto_compile_scss: true,
79 watch_files: vec![
80 "static/**/*.html".to_string(),
81 "static/**/*.css".to_string(),
82 "static/**/*.scss".to_string(),
83 "static/**/*.js".to_string(),
84 ],
85 reload_delay_ms: 100,
86 show_debug_routes: true,
87 },
88
89 prod: ProdConfig {
90 enable_tls: false,
91 cert_path: None,
92 key_path: None,
93 compression: true,
94 cache_static_files: true,
95 },
96 }
97 }
98}
99
100impl ServerConfig {
101 pub fn for_mode(mode: ServerMode, port: u16) -> Self {
103 let (hot_reload, cors_enabled, debug_logs) = match mode {
104 ServerMode::Dev => (true, true, true),
105 ServerMode::Prod => (false, false, false),
106 };
107
108 Self {
109 mode,
110 port,
111 hot_reload,
112 cors_enabled,
113 debug_logs,
114 ..Default::default()
115 }
116 }
117
118 pub fn bind_address(&self) -> String {
120 format!("{}:{}", self.host, self.port)
121 }
122
123 pub fn validate(&self) -> Result<()> {
125 if self.port < 1024 && self.host != "127.0.0.1" {
126 return Err(AppError::Validation(
127 "Ports < 1024 require root privileges".to_string(),
128 ));
129 }
130
131 if self.mode == ServerMode::Prod
132 && self.prod.enable_tls
133 && (self.prod.cert_path.is_none() || self.prod.key_path.is_none())
134 {
135 return Err(AppError::Validation(
136 "TLS enabled but cert/key paths missing".to_string(),
137 ));
138 }
139
140 Ok(())
141 }
142
143 pub fn description(&self) -> String {
145 format!(
146 "📊 Server Config\n Mode: {}\n Address: {}\n Hot-Reload: {}\n CORS: {}\n Debug: {}",
147 self.mode,
148 self.bind_address(),
149 self.hot_reload,
150 self.cors_enabled,
151 self.debug_logs
152 )
153 }
154}