feat(protocol): load_all_configs from dir with duplicate port detection
This commit is contained in:
@@ -259,6 +259,58 @@ fn find_node<'a>(doc: &'a KdlDocument, name: &str) -> Option<&'a KdlNode> {
|
||||
doc.nodes().iter().find(|n| n.name().value() == name)
|
||||
}
|
||||
|
||||
pub fn load_all_configs(dir: &Path) -> Result<Vec<ServerConfig>, ConfigError> {
|
||||
if !dir.exists() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let entries = std::fs::read_dir(dir).map_err(|e| ConfigError::Io {
|
||||
path: dir.to_path_buf(),
|
||||
source: e,
|
||||
})?;
|
||||
|
||||
let mut configs = Vec::new();
|
||||
for entry in entries {
|
||||
let entry = entry.map_err(|e| ConfigError::Io {
|
||||
path: dir.to_path_buf(),
|
||||
source: e,
|
||||
})?;
|
||||
let path = entry.path();
|
||||
if path.extension().and_then(|s| s.to_str()) != Some("kdl") {
|
||||
continue;
|
||||
}
|
||||
let name = path
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.ok_or(ConfigError::InvalidName {
|
||||
name: path.display().to_string(),
|
||||
})?
|
||||
.to_string();
|
||||
let text = std::fs::read_to_string(&path).map_err(|e| ConfigError::Io {
|
||||
path: path.clone(),
|
||||
source: e,
|
||||
})?;
|
||||
configs.push(parse_server_config(&name, &text, &path)?);
|
||||
}
|
||||
|
||||
check_duplicate_ports(&configs)?;
|
||||
Ok(configs)
|
||||
}
|
||||
|
||||
fn check_duplicate_ports(configs: &[ServerConfig]) -> Result<(), ConfigError> {
|
||||
let mut seen: std::collections::HashMap<u16, String> = std::collections::HashMap::new();
|
||||
for c in configs {
|
||||
if let Some(other) = seen.insert(c.port, c.name.clone()) {
|
||||
return Err(ConfigError::DuplicatePort {
|
||||
name_a: other,
|
||||
name_b: c.name.clone(),
|
||||
port: c.port,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -357,4 +409,39 @@ stop {
|
||||
|
||||
assert!(matches!(err, ConfigError::InvalidName { .. }));
|
||||
}
|
||||
|
||||
use std::fs;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn load_all_finds_and_parses_files() {
|
||||
let dir = tempdir().unwrap();
|
||||
fs::write(dir.path().join("a.kdl"), "command \"/bin/a\"\nport 8001").unwrap();
|
||||
fs::write(dir.path().join("b.kdl"), "command \"/bin/b\"\nport 8002").unwrap();
|
||||
fs::write(dir.path().join("ignored.txt"), "not a config").unwrap();
|
||||
let mut configs = load_all_configs(dir.path()).unwrap();
|
||||
configs.sort_by(|x, y| x.name.cmp(&y.name));
|
||||
assert_eq!(configs.len(), 2);
|
||||
assert_eq!(configs[0].name, "a");
|
||||
assert_eq!(configs[1].port, 8002);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_all_returns_empty_for_missing_dir() {
|
||||
let dir = tempdir().unwrap();
|
||||
let configs = load_all_configs(&dir.path().join("does-not-exist")).unwrap();
|
||||
assert!(configs.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_ports_detected() {
|
||||
let dir = tempdir().unwrap();
|
||||
fs::write(dir.path().join("a.kdl"), "command \"/bin/a\"\nport 8001").unwrap();
|
||||
fs::write(dir.path().join("b.kdl"), "command \"/bin/b\"\nport 8001").unwrap();
|
||||
let err = load_all_configs(dir.path()).unwrap_err();
|
||||
match err {
|
||||
ConfigError::DuplicatePort { port, .. } => assert_eq!(port, 8001),
|
||||
other => panic!("unexpected error: {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@ pub mod state;
|
||||
|
||||
pub use config::{RestartConfig, RestartPolicy, ServerConfig, StopConfig};
|
||||
pub use error::{ConfigError, RpcErrorCode};
|
||||
pub use kdl_parse::parse_server_config;
|
||||
pub use kdl_parse::{load_all_configs, parse_server_config};
|
||||
pub use state::ServerState;
|
||||
|
||||
Reference in New Issue
Block a user