use std::fmt::{self, Display, Formatter, Result as FmtResult};
use std::str::FromStr;
use serde::{Deserialize, Deserializer, Serialize};
use uuid::Uuid;
macro_rules! id_type {
($name:ident, $doc:literal, $validator:ident) => {
#[doc = $doc]
#[derive(
Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, schemars::JsonSchema,
)]
#[cfg_attr(feature = "server", derive(utoipa::ToSchema))]
pub struct $name(String);
impl $name {
pub fn new(value: impl Into<String>) -> Result<Self, IdError> {
let value = value.into();
$validator(&value)?;
Ok(Self(value))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl FromStr for $name {
type Err = IdError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
Self::new(value)
}
}
impl From<$name> for String {
fn from(value: $name) -> Self {
value.0
}
}
impl<'de> Deserialize<'de> for $name {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
Self::new(value).map_err(serde::de::Error::custom)
}
}
};
}
id_type!(
SpaceName,
"Human-visible namespace for Cards and Runs.",
validate_token
);
id_type!(CardName, "Human-visible Card name.", validate_token);
id_type!(CardUid, "Resolved immutable Card UID.", validate_uuid7);
impl CardUid {
pub fn from_uuid(uuid: uuid::Uuid) -> Result<Self, IdError> {
Self::new(uuid.to_string())
}
#[must_use]
pub fn as_uuid(&self) -> uuid::Uuid {
uuid::Uuid::parse_str(self.as_str()).expect("CardUid was validated at construction")
}
}
id_type!(
ProfileName,
"Named execution or configuration profile.",
validate_token
);
id_type!(
ExperimentUid,
"Resolved immutable Experiment UID.",
validate_uuid7
);
id_type!(ApiToken, "Opaque API token identifier.", validate_opaque);
id_type!(
IdempotencyKey,
"Idempotency key for write operations.",
validate_opaque
);
id_type!(ArtifactKey, "Artifact storage key.", validate_token);
id_type!(RoleName, "RBAC role identifier.", validate_token);
id_type!(
ConnectionName,
"Immutable tenant-scoped Operator connection name, unique per provider.",
validate_token
);
id_type!(ColumnName, "DataCard column name.", validate_card_token);
id_type!(
FeatureName,
"Feature column name referenced by a drift Verifier signal.",
validate_card_token
);
id_type!(
MediaBindingId,
"Name of a `${media:id}` binding slot in a resolved judge Prompt.",
validate_card_token
);
id_type!(SplitName, "DataCard split label.", validate_card_token);
id_type!(QueryName, "DataCard SQL query key.", validate_card_token);
id_type!(
TenantSlug,
"Human-visible tenant slug resolved to a DataTenantId at the auth boundary.",
validate_tenant_slug
);
id_type!(
PodId,
"Bifrost pod identifier for multi-pod seal coordination.",
validate_token
);
id_type!(
ProviderId,
"Tenant-visible gateway provider identifier using the canonical Wyrd name grammar.",
validate_token
);
id_type!(
ModelId,
"Provider-native gateway model identifier: 1..=255 UTF-8 bytes without control characters.",
validate_model_id
);
id_type!(
ProviderCredentialName,
"Tenant-unique gateway provider credential name.",
validate_token
);
id_type!(
ProviderDeploymentName,
"Tenant-unique gateway provider deployment name.",
validate_token
);
id_type!(
CredentialBindingName,
"Operator-configured gateway credential binding name.",
validate_token
);
id_type!(
SecretBackendName,
"Operator-configured external secret backend identity.",
validate_token
);
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, schemars::JsonSchema,
)]
#[serde(transparent)]
#[cfg_attr(feature = "server", derive(utoipa::ToSchema))]
pub struct DataTenantId(uuid::Uuid);
impl DataTenantId {
pub const SYSTEM_OWNER: DataTenantId = DataTenantId(uuid::Uuid::from_u128(
0x0000_0000_0000_7000_8000_0000_0000_0000,
));
#[must_use]
pub fn new_v7() -> Self {
Self(uuid::Uuid::now_v7())
}
pub fn new(value: uuid::Uuid) -> Result<Self, IdError> {
if value.get_version_num() == 7 {
Ok(Self(value))
} else {
Err(IdError::InvalidUuid7)
}
}
#[must_use]
pub fn as_uuid(&self) -> uuid::Uuid {
self.0
}
}
impl fmt::Display for DataTenantId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl FromStr for DataTenantId {
type Err = IdError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let uuid = uuid::Uuid::parse_str(value).map_err(|_| IdError::InvalidUuid7)?;
Self::new(uuid)
}
}
impl TryFrom<uuid::Uuid> for DataTenantId {
type Error = IdError;
fn try_from(value: uuid::Uuid) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl From<DataTenantId> for uuid::Uuid {
fn from(value: DataTenantId) -> Self {
value.0
}
}
impl<'de> Deserialize<'de> for DataTenantId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = uuid::Uuid::deserialize(deserializer)?;
Self::new(value).map_err(serde::de::Error::custom)
}
}
macro_rules! uuid7_id_type {
($(#[$meta:meta])* $name:ident) => {
$(#[$meta])*
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize,
schemars::JsonSchema,
)]
#[serde(transparent)]
#[cfg_attr(feature = "server", derive(utoipa::ToSchema))]
pub struct $name(Uuid);
impl $name {
#[must_use]
pub fn new_v7() -> Self {
Self(uuid::Uuid::now_v7())
}
pub fn new(value: Uuid) -> Result<Self, IdError> {
if value.get_version_num() == 7 {
Ok(Self(value))
} else {
Err(IdError::InvalidUuid7)
}
}
#[must_use]
pub fn as_uuid(&self) -> Uuid {
self.0
}
}
impl Display for $name {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
Display::fmt(&self.0, f)
}
}
impl FromStr for $name {
type Err = IdError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let uuid = uuid::Uuid::parse_str(value).map_err(|_| IdError::InvalidUuid7)?;
Self::new(uuid)
}
}
impl<'de> Deserialize<'de> for $name {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = uuid::Uuid::deserialize(deserializer)?;
Self::new(value).map_err(serde::de::Error::custom)
}
}
};
}
uuid7_id_type!(
BindingId
);
uuid7_id_type!(
VerificationRunId
);
uuid7_id_type!(
VerificationResultId
);
uuid7_id_type!(
VerificationExecutionId
);
uuid7_id_type!(
OperatorDispatchId
);
uuid7_id_type!(
OperatorConnectionId
);
#[must_use]
pub fn uuid7() -> String {
uuid::Uuid::new_v7(uuid::Timestamp::now(uuid::NoContext)).to_string()
}
fn validate_token(value: &str) -> Result<(), IdError> {
validate_token_with_min(value, 3)
}
fn validate_card_token(value: &str) -> Result<(), IdError> {
validate_token_with_min(value, 1)
}
fn validate_token_with_min(value: &str, min_len: usize) -> Result<(), IdError> {
if value.len() < min_len || value.len() > 64 {
return Err(IdError::InvalidToken);
}
let mut chars = value.chars();
let Some(first) = chars.next() else {
return Err(IdError::InvalidToken);
};
if !first.is_ascii_lowercase() {
return Err(IdError::InvalidToken);
}
if chars.all(|ch| ch.is_ascii_lowercase() || ch.is_ascii_digit() || ch == '_' || ch == '-') {
Ok(())
} else {
Err(IdError::InvalidToken)
}
}
fn validate_tenant_slug(value: &str) -> Result<(), IdError> {
if value.is_empty() || value.len() > 63 {
return Err(IdError::InvalidTenantSlug);
}
let mut chars = value.chars();
let Some(first) = chars.next() else {
return Err(IdError::InvalidTenantSlug);
};
if !first.is_ascii_lowercase() && !first.is_ascii_digit() {
return Err(IdError::InvalidTenantSlug);
}
if chars.all(|ch| ch.is_ascii_lowercase() || ch.is_ascii_digit() || ch == '_' || ch == '-') {
Ok(())
} else {
Err(IdError::InvalidTenantSlug)
}
}
fn validate_uuid7(value: &str) -> Result<(), IdError> {
let uuid = uuid::Uuid::parse_str(value).map_err(|_| IdError::InvalidUuid7)?;
if uuid.get_version_num() == 7 {
Ok(())
} else {
Err(IdError::InvalidUuid7)
}
}
fn validate_model_id(value: &str) -> Result<(), IdError> {
if value.is_empty() || value.len() > 255 || value.chars().any(char::is_control) {
return Err(IdError::InvalidModelId);
}
Ok(())
}
fn validate_opaque(value: &str) -> Result<(), IdError> {
if value.len() < 8 || value.len() > 256 || value.chars().any(char::is_whitespace) {
return Err(IdError::InvalidOpaque);
}
Ok(())
}
#[derive(Debug, thiserror::Error)]
pub enum IdError {
#[error("identifier must match [a-z][a-z0-9_-]{{2,63}}")]
InvalidToken,
#[error("identifier must be a UUIDv7")]
InvalidUuid7,
#[error("tenant slug must match [a-z0-9][a-z0-9_-]{{0,62}}")]
InvalidTenantSlug,
#[error("opaque identifier is invalid")]
InvalidOpaque,
#[error("model identifier must be 1..=255 UTF-8 bytes without control characters")]
InvalidModelId,
}