1use crate::mode::AppMode;
9use serde::Deserialize;
10use std::path::Path;
11
12pub trait IAppOptions: for<'de> Deserialize<'de> + Default + Send + Sync + 'static {}
21
22impl<T> IAppOptions for T where T: for<'de> Deserialize<'de> + Default + Send + Sync + 'static {}
23
24#[derive(Debug, Clone, Deserialize)]
30pub struct AppSection {
31 #[serde(default, rename = "Name")]
33 pub name: String,
34 #[serde(default = "default_urls", rename = "Urls")]
37 pub urls: Vec<String>,
38 #[serde(default = "default_max_body_size", rename = "MaxBodySize")]
40 pub max_body_size: usize,
41}
42
43impl Default for AppSection {
44 fn default() -> Self {
45 Self {
46 name: String::new(),
47 urls: default_urls(),
48 max_body_size: default_max_body_size(),
49 }
50 }
51}
52
53fn default_urls() -> Vec<String> {
54 vec!["http://0.0.0.0:5000".to_string()]
55}
56
57fn default_max_body_size() -> usize {
58 10 * 1024 * 1024 }
60
61#[derive(Debug, Clone, Deserialize, Default)]
63pub struct JwtSection {
64 #[serde(default, rename = "Secret")]
66 pub secret: String,
67}
68
69#[derive(Debug, Clone, Deserialize)]
71pub struct CorsSection {
72 #[serde(default = "default_origins")]
74 pub origins: Vec<String>,
75 #[serde(default = "default_cors_methods")]
77 pub methods: Vec<String>,
78 #[serde(default = "default_cors_headers")]
80 pub headers: Vec<String>,
81 #[serde(default)]
83 pub allow_credentials: bool,
84 #[serde(default = "default_max_age")]
86 pub max_age: u32,
87}
88
89impl Default for CorsSection {
90 fn default() -> Self {
91 Self {
92 origins: default_origins(),
93 methods: default_cors_methods(),
94 headers: default_cors_headers(),
95 allow_credentials: false,
96 max_age: default_max_age(),
97 }
98 }
99}
100
101fn default_origins() -> Vec<String> {
102 vec!["*".to_string()]
103}
104
105fn default_cors_methods() -> Vec<String> {
106 vec![
107 "GET".to_string(),
108 "POST".to_string(),
109 "PUT".to_string(),
110 "DELETE".to_string(),
111 "PATCH".to_string(),
112 "OPTIONS".to_string(),
113 ]
114}
115
116fn default_cors_headers() -> Vec<String> {
117 vec!["Content-Type".to_string(), "Authorization".to_string()]
118}
119
120fn default_max_age() -> u32 {
121 86400
122}
123
124#[derive(Debug, Clone, Deserialize, Default)]
130pub struct TlsSection {
131 #[serde(default, rename = "CertPath")]
133 pub cert_path: String,
134 #[serde(default, rename = "KeyPath")]
136 pub key_path: String,
137}
138
139#[derive(Debug, Clone, Deserialize, Default)]
144pub struct AppOptions {
145 #[serde(default, rename = "App")]
147 pub app: AppSection,
148 #[serde(default, rename = "Jwt")]
150 pub jwt: JwtSection,
151 #[serde(default, rename = "Cors")]
153 pub cors: CorsSection,
154 #[serde(default, rename = "Tls")]
156 pub tls: TlsSection,
157}
158
159pub fn load_appsettings(mode: AppMode) -> Option<serde_json::Value> {
168 let mut base = read_json_file("appsettings.json")?;
169
170 if mode == AppMode::Development {
171 if let Some(dev) = read_json_file("appsettings.Development.json") {
172 merge_json(&mut base, dev);
173 }
174 }
175
176 apply_env_overrides(&mut base);
178
179 Some(base)
180}
181
182fn apply_env_overrides(config: &mut serde_json::Value) {
184 for (key, value) in std::env::vars() {
185 if let Some(path) = key.strip_prefix("APP__") {
186 let segments: Vec<&str> = path.split("__").collect();
188 if segments.is_empty() {
189 continue;
190 }
191 set_json_value(config, &segments, &value);
192 }
193 }
194}
195
196fn set_json_value(obj: &mut serde_json::Value, segments: &[&str], value: &str) {
198 if segments.is_empty() {
199 return;
200 }
201
202 let key = segments[0];
203
204 if let serde_json::Value::Object(map) = obj {
205 if segments.len() == 1 {
206 let parsed =
208 serde_json::from_str(value).unwrap_or(serde_json::Value::String(value.to_string()));
209 map.insert(key.to_string(), parsed);
210 } else if let Some(child) = map.get_mut(key) {
211 set_json_value(child, &segments[1..], value);
213 } else {
214 let mut child = serde_json::json!({});
216 set_json_value(&mut child, &segments[1..], value);
217 map.insert(key.to_string(), child);
218 }
219 }
220}
221
222pub fn bind_config<T: for<'de> Deserialize<'de> + Default>(
224 config: &serde_json::Value,
225 section: &str,
226) -> T {
227 if section.is_empty() || section == "." {
228 serde_json::from_value(config.clone()).unwrap_or_default()
229 } else {
230 config
231 .get(section)
232 .map(|v| serde_json::from_value(v.clone()).unwrap_or_default())
233 .unwrap_or_default()
234 }
235}
236
237pub fn bind_root<T: for<'de> Deserialize<'de> + Default>(config: &serde_json::Value) -> T {
239 serde_json::from_value(config.clone()).unwrap_or_default()
240}
241
242fn read_json_file(path: impl AsRef<Path>) -> Option<serde_json::Value> {
247 let path = path.as_ref();
248
249 fn try_read(path: &Path) -> Option<serde_json::Value> {
251 let content = std::fs::read_to_string(path).ok()?;
252 serde_json::from_str(&content).ok()
253 }
254
255 if let Some(value) = try_read(path) {
257 return Some(value);
258 }
259
260 if let Ok(cwd) = std::env::current_dir() {
266 let mut dir = Some(cwd.as_path());
267 while let Some(d) = dir {
268 if let Ok(entries) = std::fs::read_dir(d) {
269 for entry in entries.flatten() {
270 if entry.path().is_dir() {
271 let candidate = entry.path().join(path);
272 if let Some(value) = try_read(&candidate) {
273 return Some(value);
274 }
275 }
276 }
277 }
278 dir = d.parent();
279 }
280 }
281
282 None
283}
284
285fn merge_json(base: &mut serde_json::Value, overlay: serde_json::Value) {
286 match (base, overlay) {
287 (serde_json::Value::Object(a), serde_json::Value::Object(b)) => {
288 for (k, v) in b {
289 merge_json(a.entry(k).or_insert(serde_json::Value::Null), v);
290 }
291 }
292 (a, b) => *a = b,
293 }
294}