lift main.rs business logic into the library
The chlog target-series resolution becomes changelog::series_candidates (UNRELEASED pinning, development-series default and fallbacks modeled by SeriesCandidates), PPA references get package_info::split_ppa (shared by pull and deb, now also rejecting empty parts), and SSH endpoints get context::ContextConfig::from_endpoint — so a library consumer can resolve series, validate PPAs and build context configurations without reimplementing the CLI's rules. All three carry unit tests; main.rs keeps only parsing of flags and error handling.
This commit is contained in:
@@ -117,6 +117,28 @@ pub enum ContextConfig {
|
||||
},
|
||||
}
|
||||
|
||||
impl ContextConfig {
|
||||
/// Build an SSH context configuration from an endpoint of the form
|
||||
/// `[ssh://][user@]host[:port]`.
|
||||
pub fn from_endpoint(endpoint: &str) -> Result<Self, String> {
|
||||
let re = regex::Regex::new(
|
||||
r"^(?:ssh://)?(?:(?P<user>[^@]+)@)?(?P<host>[^:/]+)(?::(?P<port>\d+))?$",
|
||||
)
|
||||
.expect("valid endpoint regex");
|
||||
let cap = re.captures(endpoint).ok_or_else(|| {
|
||||
format!("Invalid endpoint format: '{endpoint}'. Expected [ssh://][user@]host[:port]")
|
||||
})?;
|
||||
let host = cap.name("host").unwrap().as_str().to_string();
|
||||
let user = cap.name("user").map(|m| m.as_str().to_string());
|
||||
let port = cap
|
||||
.name("port")
|
||||
.map(|m| m.as_str().parse::<u16>())
|
||||
.transpose()
|
||||
.map_err(|_| "Invalid port number".to_string())?;
|
||||
Ok(ContextConfig::Ssh { host, user, port })
|
||||
}
|
||||
}
|
||||
|
||||
/// A context, allowing to run commands, read and write files, etc
|
||||
pub struct Context {
|
||||
/// Configuration for the context
|
||||
@@ -455,3 +477,57 @@ fn contextualize_spawn_error(program: &str, e: io::Error) -> io::Error {
|
||||
io::Error::new(e.kind(), format!("Could not run '{program}': {e}"))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod endpoint_tests {
|
||||
use super::*;
|
||||
|
||||
/// Every accepted endpoint spelling maps to the expected config.
|
||||
#[test]
|
||||
fn from_endpoint_parses_all_spellings() {
|
||||
assert_eq!(
|
||||
ContextConfig::from_endpoint("myhost"),
|
||||
Ok(ContextConfig::Ssh {
|
||||
host: "myhost".into(),
|
||||
user: None,
|
||||
port: None,
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
ContextConfig::from_endpoint("admin@myhost"),
|
||||
Ok(ContextConfig::Ssh {
|
||||
host: "myhost".into(),
|
||||
user: Some("admin".into()),
|
||||
port: None,
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
ContextConfig::from_endpoint("myhost:2222"),
|
||||
Ok(ContextConfig::Ssh {
|
||||
host: "myhost".into(),
|
||||
user: None,
|
||||
port: Some(2222),
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
ContextConfig::from_endpoint("ssh://admin@myhost:22"),
|
||||
Ok(ContextConfig::Ssh {
|
||||
host: "myhost".into(),
|
||||
user: Some("admin".into()),
|
||||
port: Some(22),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/// Non-numeric ports and extra segments are format errors; a
|
||||
/// non-u16 numeric port is a port error.
|
||||
#[test]
|
||||
fn from_endpoint_rejects_malformed_endpoints() {
|
||||
for bad in ["", "a/b/c", "host:notaport"] {
|
||||
let err = ContextConfig::from_endpoint(bad).unwrap_err();
|
||||
assert!(err.contains("Invalid endpoint format"), "{err}");
|
||||
}
|
||||
let err = ContextConfig::from_endpoint("host:99999").unwrap_err();
|
||||
assert_eq!(err, "Invalid port number");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user