use std::fmt;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
macro_rules! uuid_id {
($(#[$meta:meta])* $name:ident) => {
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
)]
#[serde(transparent)]
$(#[$meta])*
pub struct $name(pub Uuid);
impl $name {
#[allow(clippy::new_without_default)]
#[must_use]
pub fn new() -> Self {
Self(Uuid::now_v7())
}
#[must_use]
pub const fn nil() -> Self {
Self(Uuid::nil())
}
#[must_use]
pub const fn as_uuid(&self) -> &Uuid {
&self.0
}
#[must_use]
pub fn is_nil(&self) -> bool {
self.0.is_nil()
}
}
impl From<Uuid> for $name {
fn from(value: Uuid) -> Self {
Self(value)
}
}
impl From<$name> for Uuid {
fn from(value: $name) -> Self {
value.0
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0.hyphenated(), f)
}
}
impl std::str::FromStr for $name {
type Err = uuid::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Uuid::parse_str(s).map(Self)
}
}
};
}
macro_rules! string_id {
($(#[$meta:meta])* $name:ident) => {
#[derive(
Debug,
Clone,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
::serde::Serialize,
::serde::Deserialize,
::schemars::JsonSchema,
)]
#[serde(transparent)]
$(#[$meta])*
pub struct $name(pub String);
impl $name {
#[must_use]
pub fn new(value: impl Into<String>) -> Self {
Self(value.into())
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn into_string(self) -> String {
self.0
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl From<&str> for $name {
fn from(value: &str) -> Self {
Self(value.to_owned())
}
}
impl From<String> for $name {
fn from(value: String) -> Self {
Self(value)
}
}
impl From<$name> for String {
fn from(value: $name) -> Self {
value.0
}
}
impl AsRef<str> for $name {
fn as_ref(&self) -> &str {
&self.0
}
}
impl std::borrow::Borrow<str> for $name {
fn borrow(&self) -> &str {
&self.0
}
}
impl ::std::fmt::Display for $name {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
f.write_str(&self.0)
}
}
};
}
pub(crate) use string_id;
uuid_id! {
ConversationId
}
uuid_id! {
TurnId
}
uuid_id! {
InteractionId
}
uuid_id! {
CommandId
}
uuid_id! {
EventId
}
uuid_id! {
ReceiptId
}
uuid_id! {
BatchId
}
uuid_id! {
OutboxId
}
string_id! {
CaseId
}
string_id! {
WorkflowKey
}
string_id! {
WorkflowVersion
}
string_id! {
#[schemars(
description = "Opaque handle for one record, taken from the list of candidates offered with this turn. It is meaningless outside the turn and is never a database identifier."
)]
TargetToken
}
string_id! {
OptionId
}
string_id! {
OperationKey
}
string_id! {
ReadToolKey
}
string_id! {
AccountId
}
string_id! {
UserId
}
string_id! {
SchemaVersion
}
string_id! {
AttachmentId
}
string_id! {
#[schemars(
description = "Handle for the record the user has open, supplied by the interface with this turn."
)]
OriginToken
}
string_id! {
QuestionId
}
string_id! {
BlockId
}
string_id! {
ReadRequestId
}
string_id! {
ProviderKey
}
string_id! {
ModelKey
}
string_id! {
RedactionAuthority
}
string_id! {
AttemptId
}
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
Default,
Serialize,
Deserialize,
JsonSchema,
)]
#[serde(transparent)]
pub struct CaseRevision(pub u64);
impl CaseRevision {
pub const ZERO: Self = Self(0);
#[must_use]
pub const fn next(self) -> Self {
Self(self.0.saturating_add(1))
}
#[must_use]
pub const fn value(self) -> u64 {
self.0
}
}
impl From<u64> for CaseRevision {
fn from(value: u64) -> Self {
Self(value)
}
}
impl fmt::Display for CaseRevision {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn uuid_ids_round_trip_and_are_time_ordered() {
let a = TurnId::new();
let b = TurnId::new();
assert!(a <= b, "v7 identifiers sort by creation time");
let json = serde_json::to_string(&a).unwrap();
assert!(json.starts_with('"'));
let back: TurnId = serde_json::from_str(&json).unwrap();
assert_eq!(a, back);
assert_eq!(a.to_string().parse::<TurnId>().unwrap(), a);
assert!(TurnId::nil().is_nil());
}
#[test]
fn string_ids_are_transparent() {
let key = WorkflowKey::from("trip");
assert_eq!(serde_json::to_string(&key).unwrap(), "\"trip\"");
assert_eq!(key.as_str(), "trip");
assert_eq!(key.to_string(), "trip");
let back: WorkflowKey = serde_json::from_str("\"trip\"").unwrap();
assert_eq!(back, key);
}
#[test]
fn revision_next_and_zero() {
assert_eq!(CaseRevision::ZERO.next(), CaseRevision(1));
assert_eq!(CaseRevision(u64::MAX).next(), CaseRevision(u64::MAX));
assert!(CaseRevision(3) < CaseRevision(4));
}
}