Skip to main content

telltale_types/
units.rs

1//! Sized count and length newtypes for protocol-visible values.
2
3use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
4use std::fmt;
5
6/// Maximum allowed role index (0-based).
7pub const MAX_ROLE_INDEX: u32 = 9_999;
8/// Maximum allowed loop iteration count.
9pub const MAX_LOOP_COUNT: u32 = 1_000_000;
10/// Maximum on-wire message length in bytes (16 MiB).
11pub const MAX_MESSAGE_LEN_BYTES: u32 = 16 * 1024 * 1024;
12/// Maximum queue capacity (entries).
13pub const MAX_QUEUE_CAPACITY_COUNT: u32 = 65_536;
14/// Maximum channel capacity (bits).
15pub const MAX_CHANNEL_CAPACITY_BITS: u32 = 1_024;
16/// Maximum content store capacity (entries).
17pub const MAX_STORE_CAPACITY_COUNT: u32 = 1_000_000;
18
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct CountError {
21    pub kind: &'static str,
22    pub value: u64,
23    pub min: u64,
24    pub max: u64,
25}
26
27impl fmt::Display for CountError {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        write!(
30            f,
31            "invalid {}: {} (expected {}..={})",
32            self.kind, self.value, self.min, self.max
33        )
34    }
35}
36
37impl std::error::Error for CountError {}
38
39macro_rules! define_count {
40    ($name:ident, $doc:literal, min = $min:expr, max = $max:expr) => {
41        #[doc = $doc]
42        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
43        pub struct $name(u32);
44
45        impl $name {
46            pub const MIN: u32 = $min;
47            pub const MAX: u32 = $max;
48
49            /// Create a new value, returning an error if out of range.
50            ///
51            /// # Errors
52            ///
53            /// Returns [`CountError`] if `value` is not in `MIN..=MAX`.
54            pub fn new(value: u32) -> Result<Self, CountError> {
55                Self::try_new(value)
56            }
57
58            /// Try to create a new value, returning an error if out of range.
59            ///
60            /// # Errors
61            ///
62            /// Returns [`CountError`] if `value` is not in `MIN..=MAX`.
63            #[allow(clippy::as_conversions)] // u32 -> u64 is a safe widening conversion
64            pub fn try_new(value: u32) -> Result<Self, CountError> {
65                if !(Self::MIN..=Self::MAX).contains(&value) {
66                    return Err(CountError {
67                        kind: stringify!($name),
68                        value: value as u64,
69                        min: Self::MIN as u64,
70                        max: Self::MAX as u64,
71                    });
72                }
73                Ok(Self(value))
74            }
75
76            #[must_use]
77            pub const fn new_unchecked(value: u32) -> Self {
78                Self(value)
79            }
80
81            #[must_use]
82            pub const fn get(self) -> u32 {
83                self.0
84            }
85
86            #[must_use]
87            #[allow(clippy::as_conversions)] // u32 -> usize is safe (usize >= 32 bits)
88            pub const fn as_usize(self) -> usize {
89                self.0 as usize
90            }
91        }
92
93        impl TryFrom<u32> for $name {
94            type Error = CountError;
95
96            fn try_from(value: u32) -> Result<Self, Self::Error> {
97                Self::try_new(value)
98            }
99        }
100
101        impl TryFrom<usize> for $name {
102            type Error = CountError;
103
104            #[allow(clippy::as_conversions)] // u32 -> u64 is a safe widening conversion
105            fn try_from(value: usize) -> Result<Self, Self::Error> {
106                let value_u32 = u32::try_from(value).map_err(|_| CountError {
107                    kind: stringify!($name),
108                    value: value as u64,
109                    min: Self::MIN as u64,
110                    max: Self::MAX as u64,
111                })?;
112                Self::try_new(value_u32)
113            }
114        }
115
116        impl From<$name> for u32 {
117            fn from(value: $name) -> Self {
118                value.0
119            }
120        }
121
122        impl fmt::Display for $name {
123            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124                write!(f, "{}", self.0)
125            }
126        }
127
128        impl Serialize for $name {
129            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
130            where
131                S: Serializer,
132            {
133                serializer.serialize_u32(self.0)
134            }
135        }
136
137        impl<'de> Deserialize<'de> for $name {
138            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
139            where
140                D: Deserializer<'de>,
141            {
142                let value = u32::deserialize(deserializer)?;
143                $name::try_new(value).map_err(de::Error::custom)
144            }
145        }
146    };
147}
148
149define_count!(
150    RoleIndex,
151    "Index for a role instance (0-based).",
152    min = 0,
153    max = MAX_ROLE_INDEX
154);
155define_count!(
156    LoopCount,
157    "Count of loop iterations (bounded).",
158    min = 0,
159    max = MAX_LOOP_COUNT
160);
161define_count!(
162    MessageLenBytes,
163    "On-wire message length in bytes.",
164    min = 0,
165    max = MAX_MESSAGE_LEN_BYTES
166);
167define_count!(
168    QueueCapacity,
169    "Queue capacity (entries).",
170    min = 1,
171    max = MAX_QUEUE_CAPACITY_COUNT
172);
173define_count!(
174    ChannelCapacity,
175    "Channel capacity (bits).",
176    min = 0,
177    max = MAX_CHANNEL_CAPACITY_BITS
178);
179define_count!(
180    StoreCapacity,
181    "Content store capacity (entries).",
182    min = 1,
183    max = MAX_STORE_CAPACITY_COUNT
184);