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