1use std::fmt;
14use std::str::FromStr;
15
16#[non_exhaustive]
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub enum CloseReason {
24 CertificateRotated,
26 CertificateRevoked,
28 NoValidCertificate,
30 InternalError,
32 CertificateNotRecognized,
34 ServiceDeactivated,
36 ServiceNotApproved,
38 ServiceNotFound,
40 EnrollmentTimeout,
42 RateLimitExceeded,
44 ProtocolError,
50 Superseded,
52 Unknown(String),
57}
58
59impl CloseReason {
60 pub fn as_str(&self) -> &str {
62 match self {
63 Self::CertificateRotated => "certificate rotated",
64 Self::CertificateRevoked => "certificate revoked",
65 Self::NoValidCertificate => "no valid certificate",
66 Self::InternalError => "internal error",
67 Self::CertificateNotRecognized => "certificate not recognized",
68 Self::ServiceDeactivated => "service deactivated",
69 Self::ServiceNotApproved => "service not approved",
70 Self::ServiceNotFound => "service not found",
71 Self::EnrollmentTimeout => "enrollment timeout",
72 Self::RateLimitExceeded => "rate limit exceeded",
73 Self::ProtocolError => "protocol error",
74 Self::Superseded => "superseded by new connection",
75 Self::Unknown(s) => s,
76 }
77 }
78}
79
80impl fmt::Display for CloseReason {
81 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 f.write_str(self.as_str())
83 }
84}
85
86#[derive(Debug, thiserror::Error)]
92#[error("invalid close reason")]
93pub struct ParseCloseReasonError;
94
95impl FromStr for CloseReason {
96 type Err = ParseCloseReasonError;
97
98 fn from_str(s: &str) -> Result<Self, Self::Err> {
99 Ok(match s {
100 "certificate rotated" => Self::CertificateRotated,
101 "certificate revoked" => Self::CertificateRevoked,
102 "no valid certificate" => Self::NoValidCertificate,
103 "internal error" => Self::InternalError,
104 "certificate not recognized" => Self::CertificateNotRecognized,
105 "service deactivated" => Self::ServiceDeactivated,
106 "service not approved" => Self::ServiceNotApproved,
107 "service not found" => Self::ServiceNotFound,
108 "enrollment timeout" => Self::EnrollmentTimeout,
109 "rate limit exceeded" => Self::RateLimitExceeded,
110 "protocol error" => Self::ProtocolError,
111 "superseded by new connection" => Self::Superseded,
112 other => Self::Unknown(other.to_string()),
113 })
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 const KNOWN_VARIANTS: &[(CloseReason, &str)] = &[
123 (CloseReason::CertificateRotated, "certificate rotated"),
124 (CloseReason::CertificateRevoked, "certificate revoked"),
125 (CloseReason::NoValidCertificate, "no valid certificate"),
126 (CloseReason::InternalError, "internal error"),
127 (
128 CloseReason::CertificateNotRecognized,
129 "certificate not recognized",
130 ),
131 (CloseReason::ServiceDeactivated, "service deactivated"),
132 (CloseReason::ServiceNotApproved, "service not approved"),
133 (CloseReason::ServiceNotFound, "service not found"),
134 (CloseReason::EnrollmentTimeout, "enrollment timeout"),
135 (CloseReason::RateLimitExceeded, "rate limit exceeded"),
136 (CloseReason::Superseded, "superseded by new connection"),
137 ];
138
139 #[test]
140 fn display_produces_wire_strings() {
141 for (variant, expected) in KNOWN_VARIANTS {
142 assert_eq!(variant.to_string(), *expected);
143 }
144 }
145
146 #[test]
147 fn as_str_matches_display() {
148 for (variant, expected) in KNOWN_VARIANTS {
149 assert_eq!(variant.as_str(), *expected);
150 }
151 }
152
153 #[test]
154 fn from_str_roundtrip_known_variants() {
155 for (variant, wire_str) in KNOWN_VARIANTS {
156 let parsed: CloseReason = wire_str.parse().expect("parse should succeed");
157 assert_eq!(&parsed, variant);
158 assert_eq!(parsed.to_string(), *wire_str);
159 }
160 }
161
162 #[test]
163 fn from_str_unknown_passthrough() {
164 let parsed: CloseReason = "some future reason".parse().expect("parse should succeed");
165 assert_eq!(
166 parsed,
167 CloseReason::Unknown("some future reason".to_string())
168 );
169 assert_eq!(parsed.to_string(), "some future reason");
170 assert_eq!(parsed.as_str(), "some future reason");
171 }
172
173 #[test]
174 fn from_str_empty_string() {
175 let parsed: CloseReason = "".parse().expect("parse should succeed");
176 assert_eq!(parsed, CloseReason::Unknown(String::new()));
177 }
178
179 #[test]
180 fn equality_known_variants() {
181 assert_eq!(
182 CloseReason::CertificateRotated,
183 CloseReason::CertificateRotated
184 );
185 assert_ne!(
186 CloseReason::CertificateRotated,
187 CloseReason::CertificateRevoked
188 );
189 }
190
191 #[test]
192 fn equality_unknown_variants() {
193 assert_eq!(
194 CloseReason::Unknown("x".to_string()),
195 CloseReason::Unknown("x".to_string())
196 );
197 assert_ne!(
198 CloseReason::Unknown("x".to_string()),
199 CloseReason::Unknown("y".to_string())
200 );
201 }
202
203 #[test]
204 fn clone_works() {
205 let original = CloseReason::CertificateRotated;
206 let cloned = original.clone();
207 assert_eq!(original, cloned);
208
209 let original = CloseReason::Unknown("test".to_string());
210 let cloned = original.clone();
211 assert_eq!(original, cloned);
212 }
213}