use crate::mode::AppMode;
use serde::Deserialize;
use std::path::Path;
pub trait IAppOptions: for<'de> Deserialize<'de> + Default + Send + Sync + 'static {}
impl<T> IAppOptions for T where T: for<'de> Deserialize<'de> + Default + Send + Sync + 'static {}
#[derive(Debug, Clone, Deserialize)]
pub struct AppSection {
#[serde(default, rename = "Name")]
pub name: String,
#[serde(default = "default_urls", rename = "Urls")]
pub urls: Vec<String>,
#[serde(default = "default_max_body_size", rename = "MaxBodySize")]
pub max_body_size: usize,
}
impl Default for AppSection {
fn default() -> Self {
Self {
name: String::new(),
urls: default_urls(),
max_body_size: default_max_body_size(),
}
}
}
fn default_urls() -> Vec<String> {
vec!["http://0.0.0.0:5000".to_string()]
}
fn default_max_body_size() -> usize {
10 * 1024 * 1024 }
#[derive(Debug, Clone, Deserialize, Default)]
pub struct JwtSection {
#[serde(default, rename = "Secret")]
pub secret: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CorsSection {
#[serde(default = "default_origins")]
pub origins: Vec<String>,
#[serde(default = "default_cors_methods")]
pub methods: Vec<String>,
#[serde(default = "default_cors_headers")]
pub headers: Vec<String>,
#[serde(default)]
pub allow_credentials: bool,
#[serde(default = "default_max_age")]
pub max_age: u32,
}
impl Default for CorsSection {
fn default() -> Self {
Self {
origins: default_origins(),
methods: default_cors_methods(),
headers: default_cors_headers(),
allow_credentials: false,
max_age: default_max_age(),
}
}
}
fn default_origins() -> Vec<String> {
vec!["*".to_string()]
}
fn default_cors_methods() -> Vec<String> {
vec![
"GET".to_string(),
"POST".to_string(),
"PUT".to_string(),
"DELETE".to_string(),
"PATCH".to_string(),
"OPTIONS".to_string(),
]
}
fn default_cors_headers() -> Vec<String> {
vec!["Content-Type".to_string(), "Authorization".to_string()]
}
fn default_max_age() -> u32 {
86400
}
#[derive(Debug, Clone, Deserialize, Default)]
pub struct TlsSection {
#[serde(default, rename = "CertPath")]
pub cert_path: String,
#[serde(default, rename = "KeyPath")]
pub key_path: String,
}
#[derive(Debug, Clone, Deserialize, Default)]
pub struct AppOptions {
#[serde(default, rename = "App")]
pub app: AppSection,
#[serde(default, rename = "Jwt")]
pub jwt: JwtSection,
#[serde(default, rename = "Cors")]
pub cors: CorsSection,
#[serde(default, rename = "Tls")]
pub tls: TlsSection,
}
pub fn load_appsettings(mode: AppMode) -> Option<serde_json::Value> {
let mut base = read_json_file("appsettings.json")?;
if mode == AppMode::Development {
if let Some(dev) = read_json_file("appsettings.Development.json") {
merge_json(&mut base, dev);
}
}
apply_env_overrides(&mut base);
Some(base)
}
fn apply_env_overrides(config: &mut serde_json::Value) {
for (key, value) in std::env::vars() {
if let Some(path) = key.strip_prefix("APP__") {
let segments: Vec<&str> = path.split("__").collect();
if segments.is_empty() {
continue;
}
set_json_value(config, &segments, &value);
}
}
}
fn set_json_value(obj: &mut serde_json::Value, segments: &[&str], value: &str) {
if segments.is_empty() {
return;
}
let key = segments[0];
if let serde_json::Value::Object(map) = obj {
if segments.len() == 1 {
let parsed =
serde_json::from_str(value).unwrap_or(serde_json::Value::String(value.to_string()));
map.insert(key.to_string(), parsed);
} else if let Some(child) = map.get_mut(key) {
set_json_value(child, &segments[1..], value);
} else {
let mut child = serde_json::json!({});
set_json_value(&mut child, &segments[1..], value);
map.insert(key.to_string(), child);
}
}
}
pub fn bind_config<T: for<'de> Deserialize<'de> + Default>(
config: &serde_json::Value,
section: &str,
) -> T {
if section.is_empty() || section == "." {
serde_json::from_value(config.clone()).unwrap_or_default()
} else {
config
.get(section)
.map(|v| serde_json::from_value(v.clone()).unwrap_or_default())
.unwrap_or_default()
}
}
pub fn bind_root<T: for<'de> Deserialize<'de> + Default>(config: &serde_json::Value) -> T {
serde_json::from_value(config.clone()).unwrap_or_default()
}
fn read_json_file(path: impl AsRef<Path>) -> Option<serde_json::Value> {
let path = path.as_ref();
fn try_read(path: &Path) -> Option<serde_json::Value> {
let content = std::fs::read_to_string(path).ok()?;
serde_json::from_str(&content).ok()
}
if let Some(value) = try_read(path) {
return Some(value);
}
if let Ok(cwd) = std::env::current_dir() {
let mut dir = Some(cwd.as_path());
while let Some(d) = dir {
if let Ok(entries) = std::fs::read_dir(d) {
for entry in entries.flatten() {
if entry.path().is_dir() {
let candidate = entry.path().join(path);
if let Some(value) = try_read(&candidate) {
return Some(value);
}
}
}
}
dir = d.parent();
}
}
None
}
fn merge_json(base: &mut serde_json::Value, overlay: serde_json::Value) {
match (base, overlay) {
(serde_json::Value::Object(a), serde_json::Value::Object(b)) => {
for (k, v) in b {
merge_json(a.entry(k).or_insert(serde_json::Value::Null), v);
}
}
(a, b) => *a = b,
}
}