1use serde_repr::{Deserialize_repr, Serialize_repr};
2use std::{
3 error::Error,
4 fmt::{Display, Formatter, Result as FmtResult},
5};
6
7#[derive(Clone, Copy, Debug, Deserialize_repr, Eq, Hash, PartialEq, Serialize_repr)]
13#[non_exhaustive]
14#[repr(u16)]
15pub enum CloseCode {
16 UnknownError = 4000,
18 UnknownOpcode = 4001,
20 DecodeError = 4002,
22 NotAuthenticated = 4003,
24 AuthenticationFailed = 4004,
26 AlreadyAuthenticated = 4005,
28 InvalidSequence = 4007,
30 RateLimited = 4008,
32 SessionTimedOut = 4009,
34 InvalidShard = 4010,
36 ShardingRequired = 4011,
38 InvalidApiVersion = 4012,
40 InvalidIntents = 4013,
42 DisallowedIntents = 4014,
44}
45
46impl CloseCode {
47 pub const fn can_reconnect(self) -> bool {
94 matches!(
95 self,
96 Self::UnknownError
97 | Self::UnknownOpcode
98 | Self::DecodeError
99 | Self::NotAuthenticated
100 | Self::AlreadyAuthenticated
101 | Self::InvalidSequence
102 | Self::RateLimited
103 | Self::SessionTimedOut
104 )
105 }
106}
107
108impl Display for CloseCode {
109 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
110 f.write_str(match self {
111 CloseCode::UnknownError => "Unknown Error",
112 CloseCode::UnknownOpcode => "Unknown Opcode",
113 CloseCode::DecodeError => "Decode Error",
114 CloseCode::NotAuthenticated => "Not Authenticated",
115 CloseCode::AuthenticationFailed => "Authentication Failed",
116 CloseCode::AlreadyAuthenticated => "Already Authenticated",
117 CloseCode::InvalidSequence => "Invalid Sequence",
118 CloseCode::RateLimited => "Rate Limited",
119 CloseCode::SessionTimedOut => "Session Timed Out",
120 CloseCode::InvalidShard => "Invalid Shard",
121 CloseCode::ShardingRequired => "Sharding Required",
122 CloseCode::InvalidApiVersion => "Invalid Api Version",
123 CloseCode::InvalidIntents => "Invalid Intents",
124 CloseCode::DisallowedIntents => "Disallowed Intents",
125 })
126 }
127}
128
129#[derive(Debug, Eq, PartialEq)]
130pub struct CloseCodeConversionError {
131 code: u16,
132}
133
134impl CloseCodeConversionError {
135 const fn new(code: u16) -> Self {
136 Self { code }
137 }
138
139 pub const fn code(&self) -> u16 {
140 self.code
141 }
142}
143
144impl Display for CloseCodeConversionError {
145 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
146 Display::fmt(&self.code, f)?;
147
148 f.write_str(" isn't a valid close code")
149 }
150}
151
152impl Error for CloseCodeConversionError {}
153
154impl TryFrom<u16> for CloseCode {
155 type Error = CloseCodeConversionError;
156
157 fn try_from(value: u16) -> Result<Self, Self::Error> {
158 let close_code = match value {
159 4000 => CloseCode::UnknownError,
160 4001 => CloseCode::UnknownOpcode,
161 4002 => CloseCode::DecodeError,
162 4003 => CloseCode::NotAuthenticated,
163 4004 => CloseCode::AuthenticationFailed,
164 4005 => CloseCode::AlreadyAuthenticated,
165 4007 => CloseCode::InvalidSequence,
166 4008 => CloseCode::RateLimited,
167 4009 => CloseCode::SessionTimedOut,
168 4010 => CloseCode::InvalidShard,
169 4011 => CloseCode::ShardingRequired,
170 4012 => CloseCode::InvalidApiVersion,
171 4013 => CloseCode::InvalidIntents,
172 4014 => CloseCode::DisallowedIntents,
173 _ => return Err(CloseCodeConversionError::new(value)),
174 };
175
176 Ok(close_code)
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::{CloseCode, CloseCodeConversionError};
183 use serde::{Deserialize, Serialize};
184 use serde_test::Token;
185 use static_assertions::assert_impl_all;
186 use std::{fmt::Debug, hash::Hash};
187
188 assert_impl_all!(
189 CloseCode: Clone,
190 Copy,
191 Debug,
192 Deserialize<'static>,
193 Eq,
194 Hash,
195 PartialEq,
196 Send,
197 Serialize,
198 Sync
199 );
200 assert_impl_all!(CloseCodeConversionError: Debug, Eq, PartialEq, Send, Sync);
201
202 const MAP: &[(CloseCode, u16, bool)] = &[
203 (CloseCode::UnknownError, 4000, true),
204 (CloseCode::UnknownOpcode, 4001, true),
205 (CloseCode::DecodeError, 4002, true),
206 (CloseCode::NotAuthenticated, 4003, true),
207 (CloseCode::AuthenticationFailed, 4004, false),
208 (CloseCode::AlreadyAuthenticated, 4005, true),
209 (CloseCode::InvalidSequence, 4007, true),
210 (CloseCode::RateLimited, 4008, true),
211 (CloseCode::SessionTimedOut, 4009, true),
212 (CloseCode::InvalidShard, 4010, false),
213 (CloseCode::ShardingRequired, 4011, false),
214 (CloseCode::InvalidApiVersion, 4012, false),
215 (CloseCode::InvalidIntents, 4013, false),
216 (CloseCode::DisallowedIntents, 4014, false),
217 ];
218
219 #[test]
220 fn variants() {
221 for (kind, num, can_reconnect) in MAP {
222 serde_test::assert_tokens(kind, &[Token::U16(*num)]);
223 assert_eq!(*kind, CloseCode::try_from(*num).unwrap());
224 assert_eq!(*num, *kind as u16);
225 assert!(kind.can_reconnect() == *can_reconnect)
226 }
227 }
228
229 #[test]
230 fn try_from() {
231 assert!(
232 matches!(CloseCode::try_from(5000), Err(CloseCodeConversionError { code }) if code == 5000)
233 );
234 }
235}