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