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