triblespace_core/value/schemas/
genid.rs1use crate::id::ExclusiveId;
2use crate::id::Id;
3use crate::id::NilUuidError;
4use crate::id::OwnedId;
5use crate::id::RawId;
6use crate::id_hex;
7use crate::macros::entity;
8use crate::metadata;
9use crate::metadata::{ConstDescribe, ConstId};
10use crate::repo::BlobStore;
11use crate::trible::Fragment;
12use crate::value::schemas::hash::Blake3;
13use crate::value::ToValue;
14use crate::value::TryFromValue;
15use crate::value::TryToValue;
16use crate::value::Value;
17use crate::value::ValueSchema;
18use crate::value::VALUE_LEN;
19
20use std::convert::TryInto;
21
22use hex::FromHex;
23use hex::FromHexError;
24
25#[cfg(feature = "proptest")]
26use proptest::prelude::RngCore;
27
28pub struct GenId;
33
34impl ConstId for GenId {
35 const ID: Id = id_hex!("B08EE1D45EB081E8C47618178AFE0D81");
36}
37
38impl ConstDescribe for GenId {
39 fn describe<B>(blobs: &mut B) -> Result<Fragment, B::PutError>
40 where
41 B: BlobStore<Blake3>,
42 {
43 let id = Self::ID;
44 let description = blobs.put(
45 "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 using Attribute::from_name or a hash).",
46 )?;
47 let name = blobs.put("genid")?;
48 let tribles = entity! {
49 ExclusiveId::force_ref(&id) @
50 metadata::name: name,
51 metadata::description: description,
52 metadata::tag: metadata::KIND_VALUE_SCHEMA,
53 };
54
55 #[cfg(feature = "wasm")]
56 let tribles = {
57 let mut tribles = tribles;
58 tribles += entity! { ExclusiveId::force_ref(&id) @
59 metadata::value_formatter: blobs.put(wasm_formatter::GENID_WASM)?,
60 };
61 tribles
62 };
63 Ok(tribles)
64 }
65}
66
67#[cfg(feature = "wasm")]
68mod wasm_formatter {
69 use core::fmt::Write;
70
71 use triblespace_core_macros::value_formatter;
72
73 #[value_formatter]
74 pub(crate) fn genid(raw: &[u8; 32], out: &mut impl Write) -> Result<(), u32> {
75 const TABLE: &[u8; 16] = b"0123456789ABCDEF";
76
77 let prefix_ok = raw[..16].iter().all(|&b| b == 0);
78 let bytes = if prefix_ok { &raw[16..] } else { &raw[..] };
79 for &byte in bytes {
80 let hi = (byte >> 4) as usize;
81 let lo = (byte & 0x0F) as usize;
82 out.write_char(TABLE[hi] as char).map_err(|_| 1u32)?;
83 out.write_char(TABLE[lo] as char).map_err(|_| 1u32)?;
84 }
85 Ok(())
86 }
87}
88impl ValueSchema for GenId {
89 type ValidationError = ();
90 fn validate(value: Value<Self>) -> Result<Value<Self>, Self::ValidationError> {
91 if value.raw[0..16] == [0; 16] {
92 Ok(value)
93 } else {
94 Err(())
95 }
96 }
97}
98
99#[derive(Debug, Copy, Clone, PartialEq, Eq)]
101pub enum IdParseError {
102 IsNil,
104 BadFormat,
106}
107
108impl<'a> TryFromValue<'a, GenId> for &'a RawId {
110 type Error = IdParseError;
111
112 fn try_from_value(value: &'a Value<GenId>) -> Result<Self, Self::Error> {
113 if value.raw[0..16] != [0; 16] {
114 return Err(IdParseError::BadFormat);
115 }
116 Ok(value.raw[16..32].try_into().unwrap())
117 }
118}
119
120impl TryFromValue<'_, GenId> for RawId {
121 type Error = IdParseError;
122
123 fn try_from_value(value: &Value<GenId>) -> Result<Self, Self::Error> {
124 let r: Result<&RawId, IdParseError> = value.try_from_value();
125 r.copied()
126 }
127}
128
129impl ToValue<GenId> for RawId {
130 fn to_value(self) -> Value<GenId> {
131 let mut data = [0; VALUE_LEN];
132 data[16..32].copy_from_slice(&self[..]);
133 Value::new(data)
134 }
135}
136
137impl<'a> TryFromValue<'a, GenId> for &'a Id {
139 type Error = IdParseError;
140
141 fn try_from_value(value: &'a Value<GenId>) -> Result<Self, Self::Error> {
142 if value.raw[0..16] != [0; 16] {
143 return Err(IdParseError::BadFormat);
144 }
145 if let Some(id) = Id::as_transmute_raw(value.raw[16..32].try_into().unwrap()) {
146 Ok(id)
147 } else {
148 Err(IdParseError::IsNil)
149 }
150 }
151}
152
153impl TryFromValue<'_, GenId> for Id {
154 type Error = IdParseError;
155
156 fn try_from_value(value: &Value<GenId>) -> Result<Self, Self::Error> {
157 let r: Result<&Id, IdParseError> = value.try_from_value();
158 r.copied()
159 }
160}
161
162impl ToValue<GenId> for &Id {
163 fn to_value(self) -> Value<GenId> {
164 let mut data = [0; VALUE_LEN];
165 data[16..32].copy_from_slice(&self[..]);
166 Value::new(data)
167 }
168}
169
170impl ToValue<GenId> for Id {
171 fn to_value(self) -> Value<GenId> {
172 (&self).to_value()
173 }
174}
175
176impl TryFromValue<'_, GenId> for uuid::Uuid {
177 type Error = IdParseError;
178
179 fn try_from_value(value: &Value<GenId>) -> Result<Self, Self::Error> {
180 if value.raw[0..16] != [0; 16] {
181 return Err(IdParseError::BadFormat);
182 }
183 let bytes: [u8; 16] = value.raw[16..32].try_into().unwrap();
184 Ok(uuid::Uuid::from_bytes(bytes))
185 }
186}
187
188#[derive(Debug, Copy, Clone, PartialEq, Eq)]
190pub enum ExclusiveIdError {
191 FailedParse(IdParseError),
193 FailedAquire(),
196}
197
198impl From<IdParseError> for ExclusiveIdError {
199 fn from(e: IdParseError) -> Self {
200 ExclusiveIdError::FailedParse(e)
201 }
202}
203
204impl<'a> TryFromValue<'a, GenId> for ExclusiveId {
205 type Error = ExclusiveIdError;
206
207 fn try_from_value(value: &'a Value<GenId>) -> Result<Self, Self::Error> {
208 let id: Id = value.try_from_value()?;
209 id.aquire().ok_or(ExclusiveIdError::FailedAquire())
210 }
211}
212
213impl ToValue<GenId> for ExclusiveId {
214 fn to_value(self) -> Value<GenId> {
215 self.id.to_value()
216 }
217}
218
219impl ToValue<GenId> for &ExclusiveId {
220 fn to_value(self) -> Value<GenId> {
221 self.id.to_value()
222 }
223}
224
225impl TryFromValue<'_, GenId> for String {
226 type Error = IdParseError;
227
228 fn try_from_value(v: &'_ Value<GenId>) -> Result<Self, Self::Error> {
229 let id: Id = v.try_from_value()?;
230 let mut s = String::new();
231 s.push_str("genid:");
232 s.push_str(&hex::encode(id));
233 Ok(s)
234 }
235}
236
237impl ToValue<GenId> for OwnedId<'_> {
238 fn to_value(self) -> Value<GenId> {
239 self.id.to_value()
240 }
241}
242
243impl ToValue<GenId> for &OwnedId<'_> {
244 fn to_value(self) -> Value<GenId> {
245 self.id.to_value()
246 }
247}
248
249#[derive(Debug, Clone, Copy, PartialEq)]
253pub enum PackIdError {
254 BadProtocol,
256 BadHex(FromHexError),
258}
259
260impl From<FromHexError> for PackIdError {
261 fn from(value: FromHexError) -> Self {
262 PackIdError::BadHex(value)
263 }
264}
265
266impl TryToValue<GenId> for &str {
267 type Error = PackIdError;
268
269 fn try_to_value(self) -> Result<Value<GenId>, Self::Error> {
270 let protocol = "genid:";
271 if !self.starts_with(protocol) {
272 return Err(PackIdError::BadProtocol);
273 }
274 let id = RawId::from_hex(&self[protocol.len()..])?;
275 Ok(id.to_value())
276 }
277}
278
279impl TryToValue<GenId> for uuid::Uuid {
280 type Error = NilUuidError;
281
282 fn try_to_value(self) -> Result<Value<GenId>, Self::Error> {
283 let mut data = [0; VALUE_LEN];
284 data[16..32].copy_from_slice(self.as_bytes());
285 Ok(Value::new(data))
286 }
287}
288
289impl TryToValue<GenId> for &uuid::Uuid {
290 type Error = NilUuidError;
291
292 fn try_to_value(self) -> Result<Value<GenId>, Self::Error> {
293 (*self).try_to_value()
294 }
295}
296
297#[cfg(feature = "proptest")]
298pub struct IdValueTree(RawId);
300
301#[cfg(feature = "proptest")]
302#[derive(Debug)]
304pub struct RandomGenId();
305#[cfg(feature = "proptest")]
306impl proptest::strategy::Strategy for RandomGenId {
307 type Tree = IdValueTree;
308 type Value = RawId;
309
310 fn new_tree(
311 &self,
312 runner: &mut proptest::prelude::prop::test_runner::TestRunner,
313 ) -> proptest::prelude::prop::strategy::NewTree<Self> {
314 let rng = runner.rng();
315 let mut id = [0; 16];
316 rng.fill_bytes(&mut id[..]);
317
318 Ok(IdValueTree(id))
319 }
320}
321
322#[cfg(feature = "proptest")]
323impl proptest::strategy::ValueTree for IdValueTree {
324 type Value = RawId;
325
326 fn simplify(&mut self) -> bool {
327 false
328 }
329 fn complicate(&mut self) -> bool {
330 false
331 }
332 fn current(&self) -> RawId {
333 self.0
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use super::GenId;
340 use crate::id::rngid;
341 use crate::value::TryFromValue;
342 use crate::value::TryToValue;
343 use crate::value::ValueSchema;
344
345 #[test]
346 fn unique() {
347 assert!(rngid() != rngid());
348 }
349
350 #[test]
351 fn uuid_nil_round_trip() {
352 let uuid = uuid::Uuid::nil();
353 let value = uuid.try_to_value().expect("uuid packing should succeed");
354 GenId::validate(value).expect("schema validation");
355 let round_trip = uuid::Uuid::try_from_value(&value).expect("uuid unpacking should succeed");
356 assert_eq!(uuid, round_trip);
357 }
358}