triblespace_core/inline/encodings/
genid.rs1use crate::inline::Encodes;
2use crate::id::ExclusiveId;
3use crate::id::Id;
4use crate::id::NilUuidError;
5use crate::id::OwnedId;
6use crate::id::RawId;
7use crate::id_hex;
8use crate::macros::entity;
9use crate::metadata;
10use crate::metadata::MetaDescribe;
11use crate::trible::Fragment;
12use crate::inline::IntoInline;
13use crate::inline::TryFromInline;
14use crate::inline::TryToInline;
15use crate::inline::Inline;
16use crate::inline::InlineEncoding;
17use crate::inline::INLINE_LEN;
18
19use std::convert::TryInto;
20
21use hex::FromHex;
22use hex::FromHexError;
23
24#[cfg(feature = "proptest")]
25use proptest::prelude::RngCore;
26
27pub struct GenId;
32
33impl MetaDescribe for GenId {
34 fn describe() -> Fragment {
35 let id: Id = id_hex!("B08EE1D45EB081E8C47618178AFE0D81");
36 #[allow(unused_mut)]
37 let mut tribles = entity! {
38 ExclusiveId::force_ref(&id) @
39 metadata::name: "genid",
40 metadata::description: "Opaque 128-bit identifier stored in the lower 16 bytes; the upper 16 bytes are zero. The value is intended to be high-entropy and stable over time.\n\nUse for entity ids, references, or user-assigned identifiers when the bytes do not carry meaning. If you want content-derived identifiers or deduplication, use a Hash schema instead.\n\nGenId does not imply ordering or integrity. If you need deterministic ids across systems, derive them from agreed inputs (for example by wrapping the inputs in `entity!{}` and taking its `root()`, or by hashing them directly).",
41 metadata::tag: metadata::KIND_INLINE_ENCODING,
42 };
43
44 #[cfg(feature = "wasm")]
45 {
46 tribles += entity! { ExclusiveId::force_ref(&id) @
47 metadata::value_formatter: wasm_formatter::GENID_WASM,
48 };
49 }
50 tribles
51 }
52}
53
54#[cfg(feature = "wasm")]
55mod wasm_formatter {
56 use core::fmt::Write;
57
58 use triblespace_core_macros::value_formatter;
59
60 #[value_formatter]
61 pub(crate) fn genid(raw: &[u8; 32], out: &mut impl Write) -> Result<(), u32> {
62 const TABLE: &[u8; 16] = b"0123456789ABCDEF";
63
64 let prefix_ok = raw[..16].iter().all(|&b| b == 0);
65 let bytes = if prefix_ok { &raw[16..] } else { &raw[..] };
66 for &byte in bytes {
67 let hi = (byte >> 4) as usize;
68 let lo = (byte & 0x0F) as usize;
69 out.write_char(TABLE[hi] as char).map_err(|_| 1u32)?;
70 out.write_char(TABLE[lo] as char).map_err(|_| 1u32)?;
71 }
72 Ok(())
73 }
74}
75impl InlineEncoding for GenId {
76 type ValidationError = ();
77 type Encoding = Self;
78 fn validate(value: Inline<Self>) -> Result<Inline<Self>, Self::ValidationError> {
79 if value.raw[0..16] == [0; 16] {
80 Ok(value)
81 } else {
82 Err(())
83 }
84 }
85}
86
87#[derive(Debug, Copy, Clone, PartialEq, Eq)]
89pub enum IdParseError {
90 IsNil,
92 BadFormat,
94}
95
96impl<'a> TryFromInline<'a, GenId> for &'a RawId {
98 type Error = IdParseError;
99
100 fn try_from_inline(value: &'a Inline<GenId>) -> Result<Self, Self::Error> {
101 if value.raw[0..16] != [0; 16] {
102 return Err(IdParseError::BadFormat);
103 }
104 Ok(value.raw[16..32].try_into().unwrap())
105 }
106}
107
108impl TryFromInline<'_, GenId> for RawId {
109 type Error = IdParseError;
110
111 fn try_from_inline(value: &Inline<GenId>) -> Result<Self, Self::Error> {
112 let r: Result<&RawId, IdParseError> = value.try_from_inline();
113 r.copied()
114 }
115}
116
117impl Encodes<RawId> for GenId
118{
119 type Output = Inline<GenId>;
120 fn encode(source: RawId) -> Inline<GenId> {
121 let mut data = [0; INLINE_LEN];
122 data[16..32].copy_from_slice(&source[..]);
123 Inline::new(data)
124 }
125}
126
127impl Encodes<&RawId> for GenId {
128 type Output = Inline<GenId>;
129 fn encode(source: &RawId) -> Inline<GenId> {
130 <GenId as Encodes<RawId>>::encode(*source)
131 }
132}
133
134impl<'a> TryFromInline<'a, GenId> for &'a Id {
136 type Error = IdParseError;
137
138 fn try_from_inline(value: &'a Inline<GenId>) -> Result<Self, Self::Error> {
139 if value.raw[0..16] != [0; 16] {
140 return Err(IdParseError::BadFormat);
141 }
142 if let Some(id) = Id::as_transmute_raw(value.raw[16..32].try_into().unwrap()) {
143 Ok(id)
144 } else {
145 Err(IdParseError::IsNil)
146 }
147 }
148}
149
150impl TryFromInline<'_, GenId> for Id {
151 type Error = IdParseError;
152
153 fn try_from_inline(value: &Inline<GenId>) -> Result<Self, Self::Error> {
154 let r: Result<&Id, IdParseError> = value.try_from_inline();
155 r.copied()
156 }
157}
158
159impl Encodes<&Id> for GenId
160{
161 type Output = Inline<GenId>;
162 fn encode(source: &Id) -> Inline<GenId> {
163 let mut data = [0; INLINE_LEN];
164 data[16..32].copy_from_slice(&source[..]);
165 Inline::new(data)
166 }
167}
168
169impl Encodes<Id> for GenId
170{
171 type Output = Inline<GenId>;
172 fn encode(source: Id) -> Inline<GenId> {
173 (&source).to_inline()
174 }
175}
176
177impl TryFromInline<'_, GenId> for uuid::Uuid {
178 type Error = IdParseError;
179
180 fn try_from_inline(value: &Inline<GenId>) -> Result<Self, Self::Error> {
181 if value.raw[0..16] != [0; 16] {
182 return Err(IdParseError::BadFormat);
183 }
184 let bytes: [u8; 16] = value.raw[16..32].try_into().unwrap();
185 Ok(uuid::Uuid::from_bytes(bytes))
186 }
187}
188
189#[derive(Debug, Copy, Clone, PartialEq, Eq)]
191pub enum ExclusiveIdError {
192 FailedParse(IdParseError),
194 FailedAcquire(),
197}
198
199impl From<IdParseError> for ExclusiveIdError {
200 fn from(e: IdParseError) -> Self {
201 ExclusiveIdError::FailedParse(e)
202 }
203}
204
205impl<'a> TryFromInline<'a, GenId> for ExclusiveId {
206 type Error = ExclusiveIdError;
207
208 fn try_from_inline(value: &'a Inline<GenId>) -> Result<Self, Self::Error> {
209 let id: Id = value.try_from_inline()?;
210 id.acquire().ok_or(ExclusiveIdError::FailedAcquire())
211 }
212}
213
214impl Encodes<ExclusiveId> for GenId
215{
216 type Output = Inline<GenId>;
217 fn encode(source: ExclusiveId) -> Inline<GenId> {
218 source.id.to_inline()
219 }
220}
221
222impl Encodes<&ExclusiveId> for GenId
223{
224 type Output = Inline<GenId>;
225 fn encode(source: &ExclusiveId) -> Inline<GenId> {
226 source.id.to_inline()
227 }
228}
229
230impl TryFromInline<'_, GenId> for String {
231 type Error = IdParseError;
232
233 fn try_from_inline(v: &'_ Inline<GenId>) -> Result<Self, Self::Error> {
234 let id: Id = v.try_from_inline()?;
235 let mut s = String::new();
236 s.push_str("genid:");
237 s.push_str(&hex::encode(id));
238 Ok(s)
239 }
240}
241
242impl Encodes<OwnedId<'_>> for GenId
243{
244 type Output = Inline<GenId>;
245 fn encode(source: OwnedId<'_>) -> Inline<GenId> {
246 source.id.to_inline()
247 }
248}
249
250impl Encodes<&OwnedId<'_>> for GenId
251{
252 type Output = Inline<GenId>;
253 fn encode(source: &OwnedId<'_>) -> Inline<GenId> {
254 source.id.to_inline()
255 }
256}
257
258#[derive(Debug, Clone, Copy, PartialEq)]
262pub enum PackIdError {
263 BadProtocol,
265 BadHex(FromHexError),
267}
268
269impl From<FromHexError> for PackIdError {
270 fn from(value: FromHexError) -> Self {
271 PackIdError::BadHex(value)
272 }
273}
274
275impl TryToInline<GenId> for &str {
276 type Error = PackIdError;
277
278 fn try_to_inline(self) -> Result<Inline<GenId>, Self::Error> {
279 let protocol = "genid:";
280 if !self.starts_with(protocol) {
281 return Err(PackIdError::BadProtocol);
282 }
283 let id = RawId::from_hex(&self[protocol.len()..])?;
284 Ok(id.to_inline())
285 }
286}
287
288impl TryToInline<GenId> for uuid::Uuid {
289 type Error = NilUuidError;
290
291 fn try_to_inline(self) -> Result<Inline<GenId>, Self::Error> {
292 let mut data = [0; INLINE_LEN];
293 data[16..32].copy_from_slice(self.as_bytes());
294 Ok(Inline::new(data))
295 }
296}
297
298impl TryToInline<GenId> for &uuid::Uuid {
299 type Error = NilUuidError;
300
301 fn try_to_inline(self) -> Result<Inline<GenId>, Self::Error> {
302 (*self).try_to_inline()
303 }
304}
305
306#[cfg(feature = "proptest")]
307pub struct IdValueTree(RawId);
309
310#[cfg(feature = "proptest")]
311#[derive(Debug)]
313pub struct RandomGenId();
314#[cfg(feature = "proptest")]
315impl proptest::strategy::Strategy for RandomGenId {
316 type Tree = IdValueTree;
317 type Value = RawId;
318
319 fn new_tree(
320 &self,
321 runner: &mut proptest::prelude::prop::test_runner::TestRunner,
322 ) -> proptest::prelude::prop::strategy::NewTree<Self> {
323 let rng = runner.rng();
324 let mut id = [0; 16];
325 rng.fill_bytes(&mut id[..]);
326
327 Ok(IdValueTree(id))
328 }
329}
330
331#[cfg(feature = "proptest")]
332impl proptest::strategy::ValueTree for IdValueTree {
333 type Value = RawId;
334
335 fn simplify(&mut self) -> bool {
336 false
337 }
338 fn complicate(&mut self) -> bool {
339 false
340 }
341 fn current(&self) -> RawId {
342 self.0
343 }
344}
345
346#[cfg(test)]
347mod tests {
348 use super::GenId;
349 use crate::id::rngid;
350 use crate::inline::TryFromInline;
351 use crate::inline::TryToInline;
352 use crate::inline::InlineEncoding;
353
354 #[test]
355 fn unique() {
356 assert!(rngid() != rngid());
357 }
358
359 #[test]
360 fn uuid_nil_round_trip() {
361 let uuid = uuid::Uuid::nil();
362 let value = uuid.try_to_inline().expect("uuid packing should succeed");
363 GenId::validate(value).expect("schema validation");
364 let round_trip = uuid::Uuid::try_from_inline(&value).expect("uuid unpacking should succeed");
365 assert_eq!(uuid, round_trip);
366 }
367}