use std::str::FromStr;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("unrecognized OpenVPN state: {0:?}")]
pub struct ParseOpenVpnStateError(pub String);
#[derive(Debug, Clone, PartialEq, Eq, strum::Display)]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
pub enum OpenVpnState {
Connecting,
Wait,
Auth,
GetConfig,
AssignIp,
AddRoutes,
Connected,
Reconnecting,
Exiting,
TcpConnect,
Resolve,
AuthPending,
#[strum(default)]
Unknown(String),
}
impl FromStr for OpenVpnState {
type Err = ParseOpenVpnStateError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
match input {
"CONNECTING" => Ok(Self::Connecting),
"WAIT" => Ok(Self::Wait),
"AUTH" => Ok(Self::Auth),
"GET_CONFIG" => Ok(Self::GetConfig),
"ASSIGN_IP" => Ok(Self::AssignIp),
"ADD_ROUTES" => Ok(Self::AddRoutes),
"CONNECTED" => Ok(Self::Connected),
"RECONNECTING" => Ok(Self::Reconnecting),
"EXITING" => Ok(Self::Exiting),
"TCP_CONNECT" => Ok(Self::TcpConnect),
"RESOLVE" => Ok(Self::Resolve),
"AUTH_PENDING" => Ok(Self::AuthPending),
other => Err(ParseOpenVpnStateError(other.to_string())),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const ALL_KNOWN: &[(&str, OpenVpnState)] = &[
("CONNECTING", OpenVpnState::Connecting),
("WAIT", OpenVpnState::Wait),
("AUTH", OpenVpnState::Auth),
("GET_CONFIG", OpenVpnState::GetConfig),
("ASSIGN_IP", OpenVpnState::AssignIp),
("ADD_ROUTES", OpenVpnState::AddRoutes),
("CONNECTED", OpenVpnState::Connected),
("RECONNECTING", OpenVpnState::Reconnecting),
("EXITING", OpenVpnState::Exiting),
("TCP_CONNECT", OpenVpnState::TcpConnect),
("RESOLVE", OpenVpnState::Resolve),
("AUTH_PENDING", OpenVpnState::AuthPending),
];
#[test]
fn parse_all_known_states() {
for (wire, expected) in ALL_KNOWN {
assert_eq!(
wire.parse::<OpenVpnState>().unwrap(),
*expected,
"failed for {wire}"
);
}
}
#[test]
fn display_roundtrip() {
for (_, variant) in ALL_KNOWN {
let s = variant.to_string();
assert_eq!(s.parse::<OpenVpnState>().unwrap(), *variant);
}
}
#[test]
fn unknown_state_is_err() {
assert!("BOGUS".parse::<OpenVpnState>().is_err());
assert!("connected".parse::<OpenVpnState>().is_err()); }
#[test]
fn display_unknown() {
let u = OpenVpnState::Unknown("FUTURE_STATE".to_string());
assert_eq!(u.to_string(), "FUTURE_STATE");
}
#[test]
fn error_preserves_input() {
let err = "NOPE".parse::<OpenVpnState>().unwrap_err();
assert_eq!(err.0, "NOPE");
assert!(err.to_string().contains("NOPE"));
}
}