#[derive(Debug, Clone, PartialEq)]
pub struct Query {
pub user: Option<String>,
pub hosts: Vec<String>,
pub long: bool,
pub port: u16,
}
pub const DEFAULT_PORT: u16 = 79;
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum QueryError {
#[error("invalid query: empty hostname in '{input}'")]
EmptyHostname { input: String },
}
impl Query {
pub fn parse(input: Option<&str>, long: bool, port: u16) -> Result<Query, QueryError> {
let input = input.unwrap_or("");
if input.is_empty() {
return Ok(Query {
user: None,
hosts: vec!["localhost".to_string()],
long,
port,
});
}
let parts: Vec<&str> = input.splitn(2, '@').collect();
if parts.len() == 1 {
return Ok(Query {
user: Some(parts[0].to_string()),
hosts: vec!["localhost".to_string()],
long,
port,
});
}
let user = if parts[0].is_empty() {
None
} else {
Some(parts[0].to_string())
};
let hosts: Vec<String> = parts[1].split('@').map(|s| s.to_string()).collect();
if hosts.iter().any(|h| h.is_empty()) {
return Err(QueryError::EmptyHostname {
input: input.to_string(),
});
}
Ok(Query {
user,
hosts,
long,
port,
})
}
pub fn target_host(&self) -> &str {
self.hosts.last().expect("hosts must not be empty")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_user_at_host() {
let q = Query::parse(Some("user@host"), false, 79).unwrap();
assert_eq!(q.user, Some("user".to_string()));
assert_eq!(q.hosts, vec!["host".to_string()]);
assert!(!q.long);
assert_eq!(q.port, 79);
}
#[test]
fn parse_at_host_lists_users() {
let q = Query::parse(Some("@host"), false, 79).unwrap();
assert_eq!(q.user, None);
assert_eq!(q.hosts, vec!["host".to_string()]);
}
#[test]
fn parse_user_only_defaults_to_localhost() {
let q = Query::parse(Some("user"), false, 79).unwrap();
assert_eq!(q.user, Some("user".to_string()));
assert_eq!(q.hosts, vec!["localhost".to_string()]);
}
#[test]
fn parse_empty_string_defaults_to_localhost() {
let q = Query::parse(Some(""), false, 79).unwrap();
assert_eq!(q.user, None);
assert_eq!(q.hosts, vec!["localhost".to_string()]);
}
#[test]
fn parse_none_defaults_to_localhost() {
let q = Query::parse(None, false, 79).unwrap();
assert_eq!(q.user, None);
assert_eq!(q.hosts, vec!["localhost".to_string()]);
}
#[test]
fn parse_forwarding_chain() {
let q = Query::parse(Some("user@host1@host2"), false, 79).unwrap();
assert_eq!(q.user, Some("user".to_string()));
assert_eq!(q.hosts, vec!["host1".to_string(), "host2".to_string()]);
}
#[test]
fn parse_forwarding_chain_no_user() {
let q = Query::parse(Some("@host1@host2"), false, 79).unwrap();
assert_eq!(q.user, None);
assert_eq!(q.hosts, vec!["host1".to_string(), "host2".to_string()]);
}
#[test]
fn parse_long_flag_preserved() {
let q = Query::parse(Some("user@host"), true, 79).unwrap();
assert!(q.long);
}
#[test]
fn parse_custom_port_preserved() {
let q = Query::parse(Some("user@host"), false, 7979).unwrap();
assert_eq!(q.port, 7979);
}
#[test]
fn target_host_returns_last_host() {
let q = Query::parse(Some("user@host1@host2"), false, 79).unwrap();
assert_eq!(q.target_host(), "host2");
}
#[test]
fn target_host_single_host() {
let q = Query::parse(Some("user@host"), false, 79).unwrap();
assert_eq!(q.target_host(), "host");
}
#[test]
fn parse_three_host_chain() {
let q = Query::parse(Some("user@a@b@c"), false, 79).unwrap();
assert_eq!(q.user, Some("user".to_string()));
assert_eq!(
q.hosts,
vec!["a".to_string(), "b".to_string(), "c".to_string()]
);
assert_eq!(q.target_host(), "c");
}
#[test]
fn parse_trailing_at_is_error() {
let result = Query::parse(Some("user@"), false, 79);
assert_eq!(
result,
Err(QueryError::EmptyHostname {
input: "user@".to_string()
})
);
}
#[test]
fn parse_bare_at_is_error() {
let result = Query::parse(Some("@"), false, 79);
assert_eq!(
result,
Err(QueryError::EmptyHostname {
input: "@".to_string()
})
);
}
#[test]
fn parse_trailing_at_in_chain_is_error() {
let result = Query::parse(Some("user@host@"), false, 79);
assert_eq!(
result,
Err(QueryError::EmptyHostname {
input: "user@host@".to_string()
})
);
}
#[test]
fn parse_double_at_is_error() {
let result = Query::parse(Some("user@@host"), false, 79);
assert_eq!(
result,
Err(QueryError::EmptyHostname {
input: "user@@host".to_string()
})
);
}
#[test]
fn parse_at_host_trailing_at_is_error() {
let result = Query::parse(Some("@host@"), false, 79);
assert_eq!(
result,
Err(QueryError::EmptyHostname {
input: "@host@".to_string()
})
);
}
}