twilight_model/gateway/
id.rs1use serde::{Deserialize, Serialize};
2use std::{
3 error::Error,
4 fmt::{Display, Formatter, Result as FmtResult},
5 num::NonZeroU32,
6};
7
8pub struct ShardIdParseError {
9 kind: ShardIdParseErrorType,
10}
11
12impl ShardIdParseError {
13 #[must_use = "retrieving the type has no effect if left unused"]
15 pub const fn kind(&self) -> &ShardIdParseErrorType {
16 &self.kind
17 }
18
19 #[allow(clippy::unused_self)]
21 #[must_use = "consuming the error and retrieving the source has no effect if left unused"]
22 pub fn into_source(self) -> Option<Box<dyn Error + Send + Sync>> {
23 None
24 }
25
26 #[must_use = "consuming the error into its parts has no effect if left unused"]
28 pub fn into_parts(self) -> (ShardIdParseErrorType, Option<Box<dyn Error + Send + Sync>>) {
29 (self.kind, None)
30 }
31}
32
33impl Display for ShardIdParseError {
34 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
35 match self.kind {
36 ShardIdParseErrorType::NumberGreaterOrEqualTotal { number, total } => {
37 f.write_str("ShardId's number (")?;
38 Display::fmt(&number, f)?;
39 f.write_str(") was greater or equal to its total (")?;
40 Display::fmt(&total, f)?;
41
42 f.write_str(")")
43 }
44 }
45 }
46}
47
48#[derive(Debug)]
50pub enum ShardIdParseErrorType {
51 NumberGreaterOrEqualTotal {
53 number: u32,
55 total: u32,
57 },
58}
59
60#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
95#[serde(try_from = "[u32; 2]", into = "[u32; 2]")]
96pub struct ShardId {
97 number: u32,
99 total: NonZeroU32,
101}
102
103impl ShardId {
104 pub const ONE: ShardId = ShardId::new(0, 1);
108
109 pub const fn new(number: u32, total: u32) -> Self {
131 assert!(number < total, "number must be less than total");
132 if let Some(total) = NonZeroU32::new(total) {
133 Self { number, total }
134 } else {
135 panic!("unreachable: total is at least 1")
136 }
137 }
138
139 #[allow(clippy::missing_panics_doc)]
141 pub const fn new_checked(number: u32, total: u32) -> Option<Self> {
142 if number >= total {
143 return None;
144 }
145
146 if let Some(total) = NonZeroU32::new(total) {
147 Some(Self { number, total })
148 } else {
149 panic!("unreachable: total is at least 1")
150 }
151 }
152
153 pub const fn number(self) -> u32 {
155 self.number
156 }
157
158 pub const fn total(self) -> u32 {
160 self.total.get()
161 }
162}
163
164impl Display for ShardId {
168 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
169 f.debug_list()
170 .entries(Into::<[u32; 2]>::into(*self))
171 .finish()
172 }
173}
174
175impl TryFrom<[u32; 2]> for ShardId {
176 type Error = ShardIdParseError;
177
178 fn try_from([number, total]: [u32; 2]) -> Result<Self, Self::Error> {
179 Self::new_checked(number, total).ok_or(ShardIdParseError {
180 kind: ShardIdParseErrorType::NumberGreaterOrEqualTotal { number, total },
181 })
182 }
183}
184
185impl From<ShardId> for [u32; 2] {
186 fn from(id: ShardId) -> Self {
187 [id.number(), id.total()]
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::ShardId;
194 use serde::{de::DeserializeOwned, Serialize};
195 use serde_test::Token;
196 use static_assertions::{assert_impl_all, const_assert_eq};
197 use std::{fmt::Debug, hash::Hash};
198
199 const_assert_eq!(ShardId::ONE.number(), 0);
200 const_assert_eq!(ShardId::ONE.total(), 1);
201 assert_impl_all!(
202 ShardId: Clone,
203 Copy,
204 Debug,
205 DeserializeOwned,
206 Eq,
207 Hash,
208 PartialEq,
209 Send,
210 Serialize,
211 Sync
212 );
213
214 #[test]
215 const fn checked_invalid() {
216 assert!(ShardId::new_checked(0, 1).is_some());
217 assert!(ShardId::new_checked(1, 1).is_none());
218 assert!(ShardId::new_checked(2, 1).is_none());
219 assert!(ShardId::new_checked(0, 0).is_none());
220 }
221
222 #[test]
223 const fn getters() {
224 let id = ShardId::new(2, 4);
225
226 assert!(id.number() == 2);
227 assert!(id.total() == 4);
228 }
229
230 #[test]
231 fn serde() {
232 let value = ShardId::new(0, 1);
233
234 serde_test::assert_tokens(
235 &value,
236 &[
237 Token::Tuple { len: 2 },
238 Token::U32(0),
239 Token::U32(1),
240 Token::TupleEnd,
241 ],
242 )
243 }
244
245 #[should_panic(expected = "number must be less than total")]
246 #[test]
247 const fn number_equal() {
248 ShardId::new(1, 1);
249 }
250
251 #[should_panic(expected = "number must be less than total")]
252 #[test]
253 const fn number_greater() {
254 ShardId::new(2, 1);
255 }
256}