1use std::fmt;
2use std::str::FromStr;
3
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5use thiserror::Error;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
9pub struct SessionSequence(u64);
10
11impl SessionSequence {
12 #[must_use]
14 pub const fn new(value: u64) -> Self {
15 Self(value)
16 }
17
18 #[must_use]
20 pub const fn get(self) -> u64 {
21 self.0
22 }
23
24 #[must_use]
26 pub const fn checked_next(self) -> Option<Self> {
27 match self.0.checked_add(1) {
28 Some(value) => Some(Self(value)),
29 None => None,
30 }
31 }
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
36pub enum SessionSequenceParseError {
37 #[error("session sequence must use canonical unsigned decimal text")]
39 InvalidFormat,
40 #[error("session sequence is out of range")]
42 OutOfRange,
43}
44
45impl fmt::Display for SessionSequence {
46 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
47 self.0.fmt(formatter)
48 }
49}
50
51impl FromStr for SessionSequence {
52 type Err = SessionSequenceParseError;
53
54 fn from_str(value: &str) -> Result<Self, Self::Err> {
55 if value.is_empty()
56 || !value.bytes().all(|byte| byte.is_ascii_digit())
57 || (value.len() > 1 && value.starts_with('0'))
58 {
59 return Err(SessionSequenceParseError::InvalidFormat);
60 }
61 value
62 .parse()
63 .map(Self)
64 .map_err(|_| SessionSequenceParseError::OutOfRange)
65 }
66}
67
68impl Serialize for SessionSequence {
69 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
70 where
71 S: Serializer,
72 {
73 serializer.collect_str(self)
74 }
75}
76
77impl<'de> Deserialize<'de> for SessionSequence {
78 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
79 where
80 D: Deserializer<'de>,
81 {
82 let value = String::deserialize(deserializer)?;
83 value.parse().map_err(serde::de::Error::custom)
84 }
85}