Skip to main content

tea_protocol/
version.rs

1use std::fmt;
2use std::str::FromStr;
3
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5use thiserror::Error;
6
7/// Protocol version 1.0.
8pub const PROTOCOL_V1_0: ProtocolVersion = ProtocolVersion::new(1, 0);
9
10/// The protocol version written by this crate.
11pub const CURRENT_PROTOCOL_VERSION: ProtocolVersion = PROTOCOL_V1_0;
12
13/// A canonical `major.minor` agent protocol version.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub struct ProtocolVersion {
16    major: u16,
17    minor: u16,
18}
19
20impl ProtocolVersion {
21    /// Creates a protocol version from numeric major and minor components.
22    #[must_use]
23    pub const fn new(major: u16, minor: u16) -> Self {
24        Self { major, minor }
25    }
26
27    /// Returns the major version component.
28    #[must_use]
29    pub const fn major(self) -> u16 {
30        self.major
31    }
32
33    /// Returns the minor version component.
34    #[must_use]
35    pub const fn minor(self) -> u16 {
36        self.minor
37    }
38}
39
40/// Error returned when parsing a protocol version.
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
42pub enum ProtocolVersionParseError {
43    /// The value is not canonical `major.minor` decimal text.
44    #[error("protocol version must use canonical major.minor decimal text")]
45    InvalidFormat,
46    /// A component is outside the supported integer range.
47    #[error("protocol version component is out of range")]
48    ComponentOutOfRange,
49}
50
51impl fmt::Display for ProtocolVersion {
52    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
53        write!(formatter, "{}.{}", self.major, self.minor)
54    }
55}
56
57impl FromStr for ProtocolVersion {
58    type Err = ProtocolVersionParseError;
59
60    fn from_str(value: &str) -> Result<Self, Self::Err> {
61        let (major, minor) = value
62            .split_once('.')
63            .ok_or(ProtocolVersionParseError::InvalidFormat)?;
64        if major.is_empty()
65            || minor.is_empty()
66            || minor.contains('.')
67            || !canonical_decimal(major)
68            || !canonical_decimal(minor)
69        {
70            return Err(ProtocolVersionParseError::InvalidFormat);
71        }
72        Ok(Self::new(
73            major
74                .parse()
75                .map_err(|_| ProtocolVersionParseError::ComponentOutOfRange)?,
76            minor
77                .parse()
78                .map_err(|_| ProtocolVersionParseError::ComponentOutOfRange)?,
79        ))
80    }
81}
82
83impl Serialize for ProtocolVersion {
84    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
85    where
86        S: Serializer,
87    {
88        serializer.collect_str(self)
89    }
90}
91
92impl<'de> Deserialize<'de> for ProtocolVersion {
93    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
94    where
95        D: Deserializer<'de>,
96    {
97        let value = String::deserialize(deserializer)?;
98        value.parse().map_err(serde::de::Error::custom)
99    }
100}
101
102fn canonical_decimal(value: &str) -> bool {
103    value.bytes().all(|byte| byte.is_ascii_digit()) && (value == "0" || !value.starts_with('0'))
104}