use std::net::{IpAddr, Ipv4Addr};
use std::path::PathBuf;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Config {
pub workspace_root: Option<PathBuf>,
pub listen: IpAddr,
pub http_port: u16,
pub peer_listen: IpAddr,
pub peer_port: u16,
pub debug: bool,
pub parent_pid: Option<u32>,
}
impl Config {
pub fn from_args<I>(args: I) -> Result<Self, String>
where
I: IntoIterator<Item = String>,
{
let mut workspace: Option<String> = None;
let mut port: Option<String> = None;
let mut peer_port: Option<String> = None;
let mut listen: Option<String> = None;
let mut peer_listen: Option<String> = None;
let mut debug = false;
let mut parent: Option<String> = None;
let mut iter = args.into_iter();
while let Some(arg) = iter.next() {
match arg.as_str() {
"--workspace" => workspace = Some(expect_value(&mut iter, "--workspace")?),
"--port" => port = Some(expect_value(&mut iter, "--port")?),
"--peer-port" => peer_port = Some(expect_value(&mut iter, "--peer-port")?),
"--listen" => listen = Some(expect_value(&mut iter, "--listen")?),
"--peer-listen" => {
peer_listen = Some(expect_value(&mut iter, "--peer-listen")?)
}
"--debug" => debug = true,
"--parent-pid" => parent = Some(expect_value(&mut iter, "--parent-pid")?),
other => return Err(format!("unknown argument: {other}")),
}
}
let workspace_root = match workspace {
None => None,
Some(raw) => {
let root = PathBuf::from(raw);
if !root.is_absolute() {
return Err(format!("--workspace must be absolute, got {}", root.display()));
}
if !root.is_dir() {
return Err(format!(
"--workspace names {}, which is not a directory",
root.display()
));
}
Some(root)
}
};
let port = port.ok_or_else(|| "--port is required".to_string())?;
let http_port: u16 = port
.parse()
.map_err(|e| format!("--port must be a TCP port number (1-65535): {e}"))?;
if http_port == 0 {
return Err("--port must not be 0: the workbench is served at a known port".to_string());
}
let listen: IpAddr = match listen {
Some(address) => address.parse().map_err(|e| {
format!("--listen must be an IP address to bind (e.g. 127.0.0.1 or 0.0.0.0): {e}")
})?,
None => IpAddr::V4(Ipv4Addr::LOCALHOST),
};
let peer_listen: IpAddr = match peer_listen {
Some(address) => address.parse().map_err(|e| {
format!(
"--peer-listen must be an IP address to bind (e.g. 0.0.0.0 or 192.168.1.10): {e}"
)
})?,
None => IpAddr::V4(Ipv4Addr::UNSPECIFIED),
};
let peer_port: u16 = match peer_port {
Some(value) => value
.parse()
.map_err(|e| format!("--peer-port must be a TCP port number (0-65535): {e}"))?,
None => crate::peer::service::DEFAULT_PEER_PORT,
};
if peer_port == http_port {
return Err(format!(
"--peer-port and --port are both {peer_port}; the workbench and the peer listener cannot share one port"
));
}
let parent_pid = match parent {
Some(pid) => {
let pid: u32 = pid
.parse()
.map_err(|e| format!("--parent-pid must be a process id: {e}"))?;
if pid == 0 {
return Err("--parent-pid must not be 0".to_string());
}
Some(pid)
}
None => None,
};
Ok(Self {
workspace_root,
peer_port,
peer_listen,
listen,
http_port,
debug,
parent_pid,
})
}
}
fn expect_value<I>(iter: &mut I, flag: &str) -> Result<String, String>
where
I: Iterator<Item = String>,
{
iter.next()
.ok_or_else(|| format!("{flag} requires a value"))
}
#[cfg(test)]
#[path = "config_tests.rs"]
mod tests;