use crate::id::ExclusiveId;
use crate::id::Id;
use crate::id::NilUuidError;
use crate::id::OwnedId;
use crate::id::RawId;
use crate::id_hex;
use crate::macros::entity;
use crate::metadata;
use crate::metadata::{ConstDescribe, ConstId};
use crate::repo::BlobStore;
use crate::trible::Fragment;
use crate::value::schemas::hash::Blake3;
use crate::value::ToValue;
use crate::value::TryFromValue;
use crate::value::TryToValue;
use crate::value::Value;
use crate::value::ValueSchema;
use crate::value::VALUE_LEN;
use std::convert::TryInto;
use hex::FromHex;
use hex::FromHexError;
#[cfg(feature = "proptest")]
use proptest::prelude::RngCore;
/// A value schema for an abstract 128-bit identifier.
/// This identifier is generated with high entropy and is suitable for use as a unique identifier.
///
/// See the [crate::id] module documentation for a discussion on the role of this identifier.
pub struct GenId;
impl ConstId for GenId {
const ID: Id = id_hex!("B08EE1D45EB081E8C47618178AFE0D81");
}
impl ConstDescribe for GenId {
fn describe<B>(blobs: &mut B) -> Result<Fragment, B::PutError>
where
B: BlobStore<Blake3>,
{
let id = Self::ID;
let description = blobs.put(
"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).",
)?;
let name = blobs.put("genid")?;
let tribles = entity! {
ExclusiveId::force_ref(&id) @
metadata::name: name,
metadata::description: description,
metadata::tag: metadata::KIND_VALUE_SCHEMA,
};
#[cfg(feature = "wasm")]
let tribles = {
let mut tribles = tribles;
tribles += entity! { ExclusiveId::force_ref(&id) @
metadata::value_formatter: blobs.put(wasm_formatter::GENID_WASM)?,
};
tribles
};
Ok(tribles)
}
}
#[cfg(feature = "wasm")]
mod wasm_formatter {
use core::fmt::Write;
use triblespace_core_macros::value_formatter;
#[value_formatter]
pub(crate) fn genid(raw: &[u8; 32], out: &mut impl Write) -> Result<(), u32> {
const TABLE: &[u8; 16] = b"0123456789ABCDEF";
let prefix_ok = raw[..16].iter().all(|&b| b == 0);
let bytes = if prefix_ok { &raw[16..] } else { &raw[..] };
for &byte in bytes {
let hi = (byte >> 4) as usize;
let lo = (byte & 0x0F) as usize;
out.write_char(TABLE[hi] as char).map_err(|_| 1u32)?;
out.write_char(TABLE[lo] as char).map_err(|_| 1u32)?;
}
Ok(())
}
}
impl ValueSchema for GenId {
type ValidationError = ();
fn validate(value: Value<Self>) -> Result<Value<Self>, Self::ValidationError> {
if value.raw[0..16] == [0; 16] {
Ok(value)
} else {
Err(())
}
}
}
/// Error returned when extracting an identifier from a [`Value<GenId>`].
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum IdParseError {
/// The identifier is nil (all zeros), which is reserved.
IsNil,
/// The upper 16 bytes are not zero, violating the GenId layout.
BadFormat,
}
//RawId
impl<'a> TryFromValue<'a, GenId> for &'a RawId {
type Error = IdParseError;
fn try_from_value(value: &'a Value<GenId>) -> Result<Self, Self::Error> {
if value.raw[0..16] != [0; 16] {
return Err(IdParseError::BadFormat);
}
Ok(value.raw[16..32].try_into().unwrap())
}
}
impl TryFromValue<'_, GenId> for RawId {
type Error = IdParseError;
fn try_from_value(value: &Value<GenId>) -> Result<Self, Self::Error> {
let r: Result<&RawId, IdParseError> = value.try_from_value();
r.copied()
}
}
impl ToValue<GenId> for RawId {
fn to_value(self) -> Value<GenId> {
let mut data = [0; VALUE_LEN];
data[16..32].copy_from_slice(&self[..]);
Value::new(data)
}
}
//Id
impl<'a> TryFromValue<'a, GenId> for &'a Id {
type Error = IdParseError;
fn try_from_value(value: &'a Value<GenId>) -> Result<Self, Self::Error> {
if value.raw[0..16] != [0; 16] {
return Err(IdParseError::BadFormat);
}
if let Some(id) = Id::as_transmute_raw(value.raw[16..32].try_into().unwrap()) {
Ok(id)
} else {
Err(IdParseError::IsNil)
}
}
}
impl TryFromValue<'_, GenId> for Id {
type Error = IdParseError;
fn try_from_value(value: &Value<GenId>) -> Result<Self, Self::Error> {
let r: Result<&Id, IdParseError> = value.try_from_value();
r.copied()
}
}
impl ToValue<GenId> for &Id {
fn to_value(self) -> Value<GenId> {
let mut data = [0; VALUE_LEN];
data[16..32].copy_from_slice(&self[..]);
Value::new(data)
}
}
impl ToValue<GenId> for Id {
fn to_value(self) -> Value<GenId> {
(&self).to_value()
}
}
impl TryFromValue<'_, GenId> for uuid::Uuid {
type Error = IdParseError;
fn try_from_value(value: &Value<GenId>) -> Result<Self, Self::Error> {
if value.raw[0..16] != [0; 16] {
return Err(IdParseError::BadFormat);
}
let bytes: [u8; 16] = value.raw[16..32].try_into().unwrap();
Ok(uuid::Uuid::from_bytes(bytes))
}
}
/// Error returned when extracting an [`ExclusiveId`] from a [`Value<GenId>`].
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ExclusiveIdError {
/// The raw bytes could not be interpreted as an identifier.
FailedParse(IdParseError),
/// The identifier is valid but could not be exclusively acquired
/// (another holder already owns it).
FailedAcquire(),
}
impl From<IdParseError> for ExclusiveIdError {
fn from(e: IdParseError) -> Self {
ExclusiveIdError::FailedParse(e)
}
}
impl<'a> TryFromValue<'a, GenId> for ExclusiveId {
type Error = ExclusiveIdError;
fn try_from_value(value: &'a Value<GenId>) -> Result<Self, Self::Error> {
let id: Id = value.try_from_value()?;
id.acquire().ok_or(ExclusiveIdError::FailedAcquire())
}
}
impl ToValue<GenId> for ExclusiveId {
fn to_value(self) -> Value<GenId> {
self.id.to_value()
}
}
impl ToValue<GenId> for &ExclusiveId {
fn to_value(self) -> Value<GenId> {
self.id.to_value()
}
}
impl TryFromValue<'_, GenId> for String {
type Error = IdParseError;
fn try_from_value(v: &'_ Value<GenId>) -> Result<Self, Self::Error> {
let id: Id = v.try_from_value()?;
let mut s = String::new();
s.push_str("genid:");
s.push_str(&hex::encode(id));
Ok(s)
}
}
impl ToValue<GenId> for OwnedId<'_> {
fn to_value(self) -> Value<GenId> {
self.id.to_value()
}
}
impl ToValue<GenId> for &OwnedId<'_> {
fn to_value(self) -> Value<GenId> {
self.id.to_value()
}
}
/// Error returned when packing a string into a [`Value<GenId>`].
///
/// The expected format is `"genid:<32 hex chars>"`.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PackIdError {
/// The string does not start with `"genid:"`.
BadProtocol,
/// The hex portion could not be decoded.
BadHex(FromHexError),
}
impl From<FromHexError> for PackIdError {
fn from(value: FromHexError) -> Self {
PackIdError::BadHex(value)
}
}
impl TryToValue<GenId> for &str {
type Error = PackIdError;
fn try_to_value(self) -> Result<Value<GenId>, Self::Error> {
let protocol = "genid:";
if !self.starts_with(protocol) {
return Err(PackIdError::BadProtocol);
}
let id = RawId::from_hex(&self[protocol.len()..])?;
Ok(id.to_value())
}
}
impl TryToValue<GenId> for uuid::Uuid {
type Error = NilUuidError;
fn try_to_value(self) -> Result<Value<GenId>, Self::Error> {
let mut data = [0; VALUE_LEN];
data[16..32].copy_from_slice(self.as_bytes());
Ok(Value::new(data))
}
}
impl TryToValue<GenId> for &uuid::Uuid {
type Error = NilUuidError;
fn try_to_value(self) -> Result<Value<GenId>, Self::Error> {
(*self).try_to_value()
}
}
#[cfg(feature = "proptest")]
/// Proptest value tree for a random [`GenId`]. Does not shrink.
pub struct IdValueTree(RawId);
#[cfg(feature = "proptest")]
/// Proptest strategy that generates random 128-bit identifiers.
#[derive(Debug)]
pub struct RandomGenId();
#[cfg(feature = "proptest")]
impl proptest::strategy::Strategy for RandomGenId {
type Tree = IdValueTree;
type Value = RawId;
fn new_tree(
&self,
runner: &mut proptest::prelude::prop::test_runner::TestRunner,
) -> proptest::prelude::prop::strategy::NewTree<Self> {
let rng = runner.rng();
let mut id = [0; 16];
rng.fill_bytes(&mut id[..]);
Ok(IdValueTree(id))
}
}
#[cfg(feature = "proptest")]
impl proptest::strategy::ValueTree for IdValueTree {
type Value = RawId;
fn simplify(&mut self) -> bool {
false
}
fn complicate(&mut self) -> bool {
false
}
fn current(&self) -> RawId {
self.0
}
}
#[cfg(test)]
mod tests {
use super::GenId;
use crate::id::rngid;
use crate::value::TryFromValue;
use crate::value::TryToValue;
use crate::value::ValueSchema;
#[test]
fn unique() {
assert!(rngid() != rngid());
}
#[test]
fn uuid_nil_round_trip() {
let uuid = uuid::Uuid::nil();
let value = uuid.try_to_value().expect("uuid packing should succeed");
GenId::validate(value).expect("schema validation");
let round_trip = uuid::Uuid::try_from_value(&value).expect("uuid unpacking should succeed");
assert_eq!(uuid, round_trip);
}
}