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 returned when extracting an identifier from a [`Value<GenId>`].
100#[derive(Debug, Copy, Clone, PartialEq, Eq)]
101pub enum IdParseError {
102    /// The identifier is nil (all zeros), which is reserved.
103    IsNil,
104    /// The upper 16 bytes are not zero, violating the GenId layout.
105    BadFormat,
106}
107
108//RawId
109impl<'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
137//Id
138impl<'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/// Error returned when extracting an [`ExclusiveId`] from a [`Value<GenId>`].
189#[derive(Debug, Copy, Clone, PartialEq, Eq)]
190pub enum ExclusiveIdError {
191    /// The raw bytes could not be interpreted as an identifier.
192    FailedParse(IdParseError),
193    /// The identifier is valid but could not be exclusively acquired
194    /// (another holder already owns it).
195    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/// Error returned when packing a string into a [`Value<GenId>`].
250///
251/// The expected format is `"genid:<32 hex chars>"`.
252#[derive(Debug, Clone, Copy, PartialEq)]
253pub enum PackIdError {
254    /// The string does not start with `"genid:"`.
255    BadProtocol,
256    /// The hex portion could not be decoded.
257    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")]
298/// Proptest value tree for a random [`GenId`]. Does not shrink.
299pub struct IdValueTree(RawId);
300
301#[cfg(feature = "proptest")]
302/// Proptest strategy that generates random 128-bit identifiers.
303#[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}