use std::fmt;
use std::str::FromStr;
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CloseReason {
CertificateRotated,
CertificateRevoked,
NoValidCertificate,
InternalError,
CertificateNotRecognized,
ServiceDeactivated,
ServiceNotApproved,
ServiceNotFound,
EnrollmentTimeout,
RateLimitExceeded,
ProtocolError,
Superseded,
Unknown(String),
}
impl CloseReason {
pub fn as_str(&self) -> &str {
match self {
Self::CertificateRotated => "certificate rotated",
Self::CertificateRevoked => "certificate revoked",
Self::NoValidCertificate => "no valid certificate",
Self::InternalError => "internal error",
Self::CertificateNotRecognized => "certificate not recognized",
Self::ServiceDeactivated => "service deactivated",
Self::ServiceNotApproved => "service not approved",
Self::ServiceNotFound => "service not found",
Self::EnrollmentTimeout => "enrollment timeout",
Self::RateLimitExceeded => "rate limit exceeded",
Self::ProtocolError => "protocol error",
Self::Superseded => "superseded by new connection",
Self::Unknown(s) => s,
}
}
}
impl fmt::Display for CloseReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, thiserror::Error)]
#[error("invalid close reason")]
pub struct ParseCloseReasonError;
impl FromStr for CloseReason {
type Err = ParseCloseReasonError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"certificate rotated" => Self::CertificateRotated,
"certificate revoked" => Self::CertificateRevoked,
"no valid certificate" => Self::NoValidCertificate,
"internal error" => Self::InternalError,
"certificate not recognized" => Self::CertificateNotRecognized,
"service deactivated" => Self::ServiceDeactivated,
"service not approved" => Self::ServiceNotApproved,
"service not found" => Self::ServiceNotFound,
"enrollment timeout" => Self::EnrollmentTimeout,
"rate limit exceeded" => Self::RateLimitExceeded,
"protocol error" => Self::ProtocolError,
"superseded by new connection" => Self::Superseded,
other => Self::Unknown(other.to_string()),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
const KNOWN_VARIANTS: &[(CloseReason, &str)] = &[
(CloseReason::CertificateRotated, "certificate rotated"),
(CloseReason::CertificateRevoked, "certificate revoked"),
(CloseReason::NoValidCertificate, "no valid certificate"),
(CloseReason::InternalError, "internal error"),
(
CloseReason::CertificateNotRecognized,
"certificate not recognized",
),
(CloseReason::ServiceDeactivated, "service deactivated"),
(CloseReason::ServiceNotApproved, "service not approved"),
(CloseReason::ServiceNotFound, "service not found"),
(CloseReason::EnrollmentTimeout, "enrollment timeout"),
(CloseReason::RateLimitExceeded, "rate limit exceeded"),
(CloseReason::Superseded, "superseded by new connection"),
];
#[test]
fn display_produces_wire_strings() {
for (variant, expected) in KNOWN_VARIANTS {
assert_eq!(variant.to_string(), *expected);
}
}
#[test]
fn as_str_matches_display() {
for (variant, expected) in KNOWN_VARIANTS {
assert_eq!(variant.as_str(), *expected);
}
}
#[test]
fn from_str_roundtrip_known_variants() {
for (variant, wire_str) in KNOWN_VARIANTS {
let parsed: CloseReason = wire_str.parse().expect("parse should succeed");
assert_eq!(&parsed, variant);
assert_eq!(parsed.to_string(), *wire_str);
}
}
#[test]
fn from_str_unknown_passthrough() {
let parsed: CloseReason = "some future reason".parse().expect("parse should succeed");
assert_eq!(
parsed,
CloseReason::Unknown("some future reason".to_string())
);
assert_eq!(parsed.to_string(), "some future reason");
assert_eq!(parsed.as_str(), "some future reason");
}
#[test]
fn from_str_empty_string() {
let parsed: CloseReason = "".parse().expect("parse should succeed");
assert_eq!(parsed, CloseReason::Unknown(String::new()));
}
#[test]
fn equality_known_variants() {
assert_eq!(
CloseReason::CertificateRotated,
CloseReason::CertificateRotated
);
assert_ne!(
CloseReason::CertificateRotated,
CloseReason::CertificateRevoked
);
}
#[test]
fn equality_unknown_variants() {
assert_eq!(
CloseReason::Unknown("x".to_string()),
CloseReason::Unknown("x".to_string())
);
assert_ne!(
CloseReason::Unknown("x".to_string()),
CloseReason::Unknown("y".to_string())
);
}
#[test]
fn clone_works() {
let original = CloseReason::CertificateRotated;
let cloned = original.clone();
assert_eq!(original, cloned);
let original = CloseReason::Unknown("test".to_string());
let cloned = original.clone();
assert_eq!(original, cloned);
}
}