httpward_core/config/
loader.rs1use super::{GlobalConfig, SiteConfig};
3use super::strategy::{LegacyStrategyCollection as StrategyCollection, MiddlewareConfig};
4use anyhow::{Context, Result};
5use glob::glob;
6use std::collections::HashMap;
7use std::fs;
8use std::path::{Path, PathBuf};
9use schemars::JsonSchema;
10use tracing::info;
11
12#[derive(Debug, Clone, JsonSchema)]
14pub struct AppConfig {
15 pub global: GlobalConfig,
16 pub sites: Vec<SiteConfig>,
17}
18
19pub fn load(config_path: impl AsRef<Path>) -> Result<AppConfig> {
20 let config_path = config_path.as_ref();
21
22 let global_content =
24 fs::read_to_string(&config_path).context("Cannot read httpward.yaml config file")?;
25
26
27 let mut global: GlobalConfig = serde_yaml::from_str(&global_content)
28 .context("Cannot parse httpward.yaml config (YAML error)")?;
29
30 if let Some(strategies_from_file) = load_default_strategies(config_path.parent().unwrap_or(Path::new(".")))? {
32 for (name, middleware) in strategies_from_file {
34 if !global.strategies.contains_key(&name) {
35 global.strategies.insert(name, middleware);
36 }
37 }
38
39 }
40
41 for (index, listener) in global.listeners.iter().enumerate() {
43 if listener.port == 0 && listener.tls.is_some() {
44 panic!(
45 "Config `{}`: listener #{} has invalid port 0. Please set up the port.",
46 "httpward.yaml", index
47 );
48 }
49 }
50
51 let sites_dir = &global.sites_enabled;
53 let mut sites = Vec::new();
54
55 if sites_dir.exists() {
56 for pattern in ["*.yaml", "*.yml"] {
57 let full_pattern = sites_dir.join(pattern);
58 for entry in
59 glob(full_pattern.to_str().unwrap_or_default()).context("glob pattern error")?
60 {
61 let path = entry.context("glob entry error")?;
62
63 let content = fs::read_to_string(&path)
64 .context(format!("Cannot read site config: {:?}", path))?;
65
66 let site: SiteConfig = serde_yaml::from_str(&content)
67 .context(format!("Cannot parse site config {:?}: YAML error", path))?;
68
69 let file_name = path
70 .file_name()
71 .and_then(|n| n.to_str())
72 .unwrap_or("<unknown>");
73
74 validate_site_config(&site, file_name).context(format!(
75 "Invalid site config {:?}: no domain specified",
76 path
77 ))?;
78
79 info!("Loaded site config: {}", file_name);
80 sites.push(site);
81 }
82 }
83 }
84
85 Ok(AppConfig { global, sites })
86}
87
88fn validate_site_config(site: &SiteConfig, file_name: &str) -> Result<()> {
89 if site.domain.is_empty() && site.domains.is_empty() {
90 panic!(
91 "Error in the config: `{}`, must have at least `domain` or one entry in `domains`",
92 file_name
93 );
94 }
95
96 for (index, listener) in site.listeners.iter().enumerate() {
98 if listener.port == 0 && listener.tls.is_some() {
99 panic!(
100 "Config `{}`: listener #{} has invalid port 0. Please set up the port.",
101 file_name, index
102 );
103 }
104 }
105
106 Ok(())
107}
108
109fn load_default_strategies(base_dir: &Path) -> Result<Option<StrategyCollection>> {
112 let strategies_path = base_dir.join("strategies.yml");
113
114 if !strategies_path.exists() {
115 let strategies_yaml_path = base_dir.join("strategies.yaml");
117 if !strategies_yaml_path.exists() {
118 return Ok(None);
119 }
120 return load_strategies_from_file(&strategies_yaml_path);
121 }
122
123 load_strategies_from_file(&strategies_path)
124}
125
126fn load_strategies_from_file(strategies_path: &PathBuf) -> Result<Option<StrategyCollection>> {
128
129 let content = fs::read_to_string(strategies_path)
130 .with_context(|| format!("Cannot read strategies file: {:?}", strategies_path))?;
131
132
133 let strategies_map: HashMap<String, serde_yaml::Value> = serde_yaml::from_str(&content)
135 .with_context(|| format!("Cannot parse strategies file {:?}: YAML error", strategies_path))?;
136
137 if strategies_map.is_empty() {
139 return Ok(None);
140 }
141
142 let mut strategies = StrategyCollection::new();
144 for (name, value) in strategies_map {
145 let middleware: Vec<MiddlewareConfig> = serde_yaml::from_value(value)
147 .with_context(|| format!("Cannot parse strategy '{}'", name))?;
148
149 strategies.insert(name, middleware);
150 }
151
152 Ok(Some(strategies))
153}