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)
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -357,4 +409,39 @@ stop {
|
|||||||
|
|
||||||
assert!(matches!(err, ConfigError::InvalidName { .. }));
|
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 config::{RestartConfig, RestartPolicy, ServerConfig, StopConfig};
|
||||||
pub use error::{ConfigError, RpcErrorCode};
|
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;
|
pub use state::ServerState;
|
||||||
|
|||||||
Reference in New Issue
Block a user