Skip to main content

triblespace_core/value/schemas/
genid.rs

1use 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
31/// A value schema for an abstract 128-bit identifier.
32/// This identifier is generated with high entropy and is suitable for use as a unique identifier.
33///
34/// See the [crate::id] module documentation for a discussion on the role of this identifier.
35pub 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/// Error that can occur when parsing an identifier from a Value.
103#[derive(Debug, Copy, Clone, PartialEq, Eq)]
104pub enum IdParseError {
105    IsNil,
106    BadFormat,
107}
108
109//RawId
110impl<'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
150//Id
151impl<'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}