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,
103 BadFormat,
104}
105
106impl<'a> TryFromValue<'a, GenId> for &'a RawId {
108 type Error = IdParseError;
109
110 fn try_from_value(value: &'a Value<GenId>) -> Result<Self, Self::Error> {
111 if value.raw[0..16] != [0; 16] {
112 return Err(IdParseError::BadFormat);
113 }
114 Ok(value.raw[16..32].try_into().unwrap())
115 }
116}
117
118impl TryFromValue<'_, GenId> for RawId {
119 type Error = IdParseError;
120
121 fn try_from_value(value: &Value<GenId>) -> Result<Self, Self::Error> {
122 let r: Result<&RawId, IdParseError> = value.try_from_value();
123 r.copied()
124 }
125}
126
127impl ToValue<GenId> for RawId {
128 fn to_value(self) -> Value<GenId> {
129 let mut data = [0; VALUE_LEN];
130 data[16..32].copy_from_slice(&self[..]);
131 Value::new(data)
132 }
133}
134
135impl<'a> TryFromValue<'a, GenId> for &'a Id {
137 type Error = IdParseError;
138
139 fn try_from_value(value: &'a Value<GenId>) -> Result<Self, Self::Error> {
140 if value.raw[0..16] != [0; 16] {
141 return Err(IdParseError::BadFormat);
142 }
143 if let Some(id) = Id::as_transmute_raw(value.raw[16..32].try_into().unwrap()) {
144 Ok(id)
145 } else {
146 Err(IdParseError::IsNil)
147 }
148 }
149}
150
151impl TryFromValue<'_, GenId> for Id {
152 type Error = IdParseError;
153
154 fn try_from_value(value: &Value<GenId>) -> Result<Self, Self::Error> {
155 let r: Result<&Id, IdParseError> = value.try_from_value();
156 r.copied()
157 }
158}
159
160impl ToValue<GenId> for &Id {
161 fn to_value(self) -> Value<GenId> {
162 let mut data = [0; VALUE_LEN];
163 data[16..32].copy_from_slice(&self[..]);
164 Value::new(data)
165 }
166}
167
168impl ToValue<GenId> for Id {
169 fn to_value(self) -> Value<GenId> {
170 (&self).to_value()
171 }
172}
173
174impl TryFromValue<'_, GenId> for uuid::Uuid {
175 type Error = IdParseError;
176
177 fn try_from_value(value: &Value<GenId>) -> Result<Self, Self::Error> {
178 if value.raw[0..16] != [0; 16] {
179 return Err(IdParseError::BadFormat);
180 }
181 let bytes: [u8; 16] = value.raw[16..32].try_into().unwrap();
182 Ok(uuid::Uuid::from_bytes(bytes))
183 }
184}
185
186#[derive(Debug, Copy, Clone, PartialEq, Eq)]
187pub enum ExclusiveIdError {
188 FailedParse(IdParseError),
189 FailedAquire(),
190}
191
192impl From<IdParseError> for ExclusiveIdError {
193 fn from(e: IdParseError) -> Self {
194 ExclusiveIdError::FailedParse(e)
195 }
196}
197
198impl<'a> TryFromValue<'a, GenId> for ExclusiveId {
199 type Error = ExclusiveIdError;
200
201 fn try_from_value(value: &'a Value<GenId>) -> Result<Self, Self::Error> {
202 let id: Id = value.try_from_value()?;
203 id.aquire().ok_or(ExclusiveIdError::FailedAquire())
204 }
205}
206
207impl ToValue<GenId> for ExclusiveId {
208 fn to_value(self) -> Value<GenId> {
209 self.id.to_value()
210 }
211}
212
213impl ToValue<GenId> for &ExclusiveId {
214 fn to_value(self) -> Value<GenId> {
215 self.id.to_value()
216 }
217}
218
219impl TryFromValue<'_, GenId> for String {
220 type Error = IdParseError;
221
222 fn try_from_value(v: &'_ Value<GenId>) -> Result<Self, Self::Error> {
223 let id: Id = v.try_from_value()?;
224 let mut s = String::new();
225 s.push_str("genid:");
226 s.push_str(&hex::encode(id));
227 Ok(s)
228 }
229}
230
231impl ToValue<GenId> for OwnedId<'_> {
232 fn to_value(self) -> Value<GenId> {
233 self.id.to_value()
234 }
235}
236
237impl ToValue<GenId> for &OwnedId<'_> {
238 fn to_value(self) -> Value<GenId> {
239 self.id.to_value()
240 }
241}
242
243#[derive(Debug, Clone, Copy, PartialEq)]
244pub enum PackIdError {
245 BadProtocol,
246 BadHex(FromHexError),
247}
248
249impl From<FromHexError> for PackIdError {
250 fn from(value: FromHexError) -> Self {
251 PackIdError::BadHex(value)
252 }
253}
254
255impl TryToValue<GenId> for &str {
256 type Error = PackIdError;
257
258 fn try_to_value(self) -> Result<Value<GenId>, Self::Error> {
259 let protocol = "genid:";
260 if !self.starts_with(protocol) {
261 return Err(PackIdError::BadProtocol);
262 }
263 let id = RawId::from_hex(&self[protocol.len()..])?;
264 Ok(id.to_value())
265 }
266}
267
268impl TryToValue<GenId> for uuid::Uuid {
269 type Error = NilUuidError;
270
271 fn try_to_value(self) -> Result<Value<GenId>, Self::Error> {
272 let mut data = [0; VALUE_LEN];
273 data[16..32].copy_from_slice(self.as_bytes());
274 Ok(Value::new(data))
275 }
276}
277
278impl TryToValue<GenId> for &uuid::Uuid {
279 type Error = NilUuidError;
280
281 fn try_to_value(self) -> Result<Value<GenId>, Self::Error> {
282 (*self).try_to_value()
283 }
284}
285
286#[cfg(feature = "proptest")]
287pub struct IdValueTree(RawId);
288
289#[cfg(feature = "proptest")]
290#[derive(Debug)]
291pub struct RandomGenId();
292#[cfg(feature = "proptest")]
293impl proptest::strategy::Strategy for RandomGenId {
294 type Tree = IdValueTree;
295 type Value = RawId;
296
297 fn new_tree(
298 &self,
299 runner: &mut proptest::prelude::prop::test_runner::TestRunner,
300 ) -> proptest::prelude::prop::strategy::NewTree<Self> {
301 let rng = runner.rng();
302 let mut id = [0; 16];
303 rng.fill_bytes(&mut id[..]);
304
305 Ok(IdValueTree(id))
306 }
307}
308
309#[cfg(feature = "proptest")]
310impl proptest::strategy::ValueTree for IdValueTree {
311 type Value = RawId;
312
313 fn simplify(&mut self) -> bool {
314 false
315 }
316 fn complicate(&mut self) -> bool {
317 false
318 }
319 fn current(&self) -> RawId {
320 self.0
321 }
322}
323
324#[cfg(test)]
325mod tests {
326 use super::GenId;
327 use crate::id::rngid;
328 use crate::value::TryFromValue;
329 use crate::value::TryToValue;
330 use crate::value::ValueSchema;
331
332 #[test]
333 fn unique() {
334 assert!(rngid() != rngid());
335 }
336
337 #[test]
338 fn uuid_nil_round_trip() {
339 let uuid = uuid::Uuid::nil();
340 let value = uuid.try_to_value().expect("uuid packing should succeed");
341 GenId::validate(value).expect("schema validation");
342 let round_trip = uuid::Uuid::try_from_value(&value).expect("uuid unpacking should succeed");
343 assert_eq!(uuid, round_trip);
344 }
345}