spacetimedb_lib/
connection_id.rs1use anyhow::Context as _;
2use core::{fmt, net::Ipv6Addr};
3use spacetimedb_bindings_macro::{Deserialize, Serialize};
4use spacetimedb_lib::from_hex_pad;
5use spacetimedb_sats::hex::HexString;
6use spacetimedb_sats::{impl_deserialize, impl_serialize, impl_st, AlgebraicType, AlgebraicValue};
7
8#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
29pub struct ConnectionId {
30 __connection_id__: u128,
31}
32
33impl_st!([] ConnectionId, AlgebraicType::connection_id());
34
35#[cfg(feature = "memory-usage")]
36impl spacetimedb_memory_usage::MemoryUsage for ConnectionId {}
37
38#[cfg(feature = "metrics_impls")]
39impl spacetimedb_metrics::typed_prometheus::AsPrometheusLabel for ConnectionId {
40 fn as_prometheus_str(&self) -> impl AsRef<str> + '_ {
41 self.to_hex()
42 }
43}
44
45impl fmt::Display for ConnectionId {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 f.pad(&self.to_hex())
48 }
49}
50
51impl fmt::Debug for ConnectionId {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 f.debug_tuple("ConnectionId").field(&format_args!("{self}")).finish()
54 }
55}
56
57impl ConnectionId {
58 pub const ZERO: Self = Self::from_u128(0);
59
60 pub const fn from_u128(__connection_id__: u128) -> Self {
61 Self { __connection_id__ }
62 }
63
64 pub const fn to_u128(&self) -> u128 {
65 self.__connection_id__
66 }
67
68 pub const fn from_le_byte_array(arr: [u8; 16]) -> Self {
75 Self::from_u128(u128::from_le_bytes(arr))
76 }
77
78 pub const fn from_be_byte_array(arr: [u8; 16]) -> Self {
93 Self::from_u128(u128::from_be_bytes(arr))
94 }
95
96 pub const fn as_le_byte_array(&self) -> [u8; 16] {
98 self.__connection_id__.to_le_bytes()
99 }
100
101 pub const fn as_be_byte_array(&self) -> [u8; 16] {
107 self.__connection_id__.to_be_bytes()
108 }
109
110 pub fn from_hex(hex: &str) -> Result<Self, anyhow::Error> {
112 from_hex_pad::<[u8; 16], _>(hex)
113 .context("ConnectionIds must be 32 hex characters (16 bytes) in length.")
114 .map(Self::from_be_byte_array)
115 }
116
117 pub fn to_hex(self) -> HexString<16> {
119 spacetimedb_sats::hex::encode(&self.as_be_byte_array())
120 }
121
122 pub fn abbreviate(&self) -> [u8; 8] {
125 self.as_be_byte_array()[..8].try_into().unwrap()
126 }
127
128 pub fn to_abbreviated_hex(self) -> HexString<8> {
130 spacetimedb_sats::hex::encode(&self.abbreviate())
131 }
132
133 pub fn from_be_slice(slice: impl AsRef<[u8]>) -> Self {
135 let slice = slice.as_ref();
136 let mut dst = [0u8; 16];
137 dst.copy_from_slice(slice);
138 Self::from_be_byte_array(dst)
139 }
140
141 pub fn to_ipv6(self) -> Ipv6Addr {
142 Ipv6Addr::from(self.__connection_id__)
143 }
144
145 #[allow(dead_code)]
146 pub fn to_ipv6_string(self) -> String {
147 self.to_ipv6().to_string()
148 }
149
150 pub fn none_if_zero(self) -> Option<Self> {
151 (self != Self::ZERO).then_some(self)
152 }
153}
154
155impl From<u128> for ConnectionId {
156 fn from(value: u128) -> Self {
157 Self::from_u128(value)
158 }
159}
160
161impl From<ConnectionId> for AlgebraicValue {
162 fn from(value: ConnectionId) -> Self {
163 AlgebraicValue::product([value.to_u128().into()])
164 }
165}
166
167#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
168pub struct ConnectionIdForUrl(u128);
169
170impl From<ConnectionId> for ConnectionIdForUrl {
171 fn from(addr: ConnectionId) -> Self {
172 ConnectionIdForUrl(addr.to_u128())
173 }
174}
175
176impl From<ConnectionIdForUrl> for ConnectionId {
177 fn from(addr: ConnectionIdForUrl) -> Self {
178 ConnectionId::from_u128(addr.0)
179 }
180}
181
182impl_serialize!([] ConnectionIdForUrl, (self, ser) => self.0.serialize(ser));
183impl_deserialize!([] ConnectionIdForUrl, de => u128::deserialize(de).map(Self));
184impl_st!([] ConnectionIdForUrl, AlgebraicType::U128);
185
186#[cfg(feature = "serde")]
187impl serde::Serialize for ConnectionIdForUrl {
188 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
189 where
190 S: serde::Serializer,
191 {
192 spacetimedb_sats::ser::serde::serialize_to(&ConnectionId::from(*self).as_be_byte_array(), serializer)
193 }
194}
195
196#[cfg(feature = "serde")]
197impl<'de> serde::Deserialize<'de> for ConnectionIdForUrl {
198 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
199 where
200 D: serde::Deserializer<'de>,
201 {
202 let arr = spacetimedb_sats::de::serde::deserialize_from(deserializer)?;
203 Ok(ConnectionId::from_be_byte_array(arr).into())
204 }
205}
206
207#[cfg(feature = "serde")]
208impl serde::Serialize for ConnectionId {
209 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
210 where
211 S: serde::Serializer,
212 {
213 spacetimedb_sats::ser::serde::serialize_to(&self.as_be_byte_array(), serializer)
214 }
215}
216
217#[cfg(feature = "serde")]
218impl<'de> serde::Deserialize<'de> for ConnectionId {
219 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
220 where
221 D: serde::Deserializer<'de>,
222 {
223 let arr = spacetimedb_sats::de::serde::deserialize_from(deserializer)?;
224 Ok(ConnectionId::from_be_byte_array(arr))
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231 use proptest::prelude::*;
232 use spacetimedb_sats::bsatn;
233 use spacetimedb_sats::ser::serde::SerializeWrapper;
234 use spacetimedb_sats::GroundSpacetimeType as _;
235
236 #[test]
237 fn connection_id_json_serialization_big_endian() {
238 let conn_id = ConnectionId::from_be_byte_array([0xff, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
239
240 let hex = conn_id.to_hex();
241 assert!(
242 hex.as_str().starts_with("ff01"),
243 "expected {hex:?} to start with \"ff01\""
244 );
245
246 let json1 = serde_json::to_string(&conn_id).unwrap();
247 let json2 = serde_json::to_string(&ConnectionIdForUrl::from(conn_id)).unwrap();
248
249 assert!(
250 json1.contains(hex.as_str()),
251 "expected {json1} to contain {hex} but it didn't"
252 );
253 assert!(
254 json2.contains(hex.as_str()),
255 "expected {json2} to contain {hex} but it didn't"
256 );
257
258 let decimal = conn_id.to_u128().to_string();
264 let json3 = serde_json::to_string(SerializeWrapper::from_ref(&conn_id)).unwrap();
265 assert!(
266 json3.contains(decimal.as_str()),
267 "expected {json3} to contain {decimal} but it didn't"
268 );
269 }
270
271 proptest! {
272 #[test]
273 fn test_bsatn_roundtrip(val: u128) {
274 let conn_id = ConnectionId::from_u128(val);
275 let ser = bsatn::to_vec(&conn_id).unwrap();
276 let de = bsatn::from_slice(&ser).unwrap();
277 assert_eq!(conn_id, de);
278 }
279
280 #[test]
281 fn connection_id_conversions(a: u128) {
282 let v = ConnectionId::from_u128(a);
283
284 prop_assert_eq!(ConnectionId::from_le_byte_array(v.as_le_byte_array()), v);
285 prop_assert_eq!(ConnectionId::from_be_byte_array(v.as_be_byte_array()), v);
286 prop_assert_eq!(ConnectionId::from_hex(v.to_hex().as_str()).unwrap(), v);
287 }
288 }
289
290 #[test]
291 fn connection_id_is_special() {
292 assert!(ConnectionId::get_type().is_special());
293 }
294
295 #[cfg(feature = "serde")]
296 mod serde {
297 use super::*;
298 use crate::sats::{algebraic_value::de::ValueDeserializer, de::Deserialize, Typespace};
299 use crate::ser::serde::SerializeWrapper;
300 use crate::WithTypespace;
301
302 proptest! {
303 #[test]
308 fn test_wrapper_roundtrip(val: u128) {
309 let conn_id = ConnectionId::from_u128(val);
310 let wrapped = SerializeWrapper::new(&conn_id);
311
312 let ser = serde_json::to_string(&wrapped).unwrap();
313 let empty = Typespace::default();
314 let conn_id_ty = ConnectionId::get_type();
315 let conn_id_ty = WithTypespace::new(&empty, &conn_id_ty);
316 let row = serde_json::from_str::<serde_json::Value>(&ser[..])?;
317 let de = ::serde::de::DeserializeSeed::deserialize(
318 crate::de::serde::SeedWrapper(
319 conn_id_ty
320 ),
321 row)?;
322 let de = ConnectionId::deserialize(ValueDeserializer::new(de)).unwrap();
323 prop_assert_eq!(conn_id, de);
324 }
325 }
326
327 proptest! {
328 #[test]
329 fn test_serde_roundtrip(val: u128) {
330 let conn_id = ConnectionId::from_u128(val);
331 let to_url = ConnectionIdForUrl::from(conn_id);
332 let ser = serde_json::to_vec(&to_url).unwrap();
333 let de = serde_json::from_slice::<ConnectionIdForUrl>(&ser).unwrap();
334 let from_url = ConnectionId::from(de);
335 prop_assert_eq!(conn_id, from_url);
336 }
337 }
338 }
339}