use std::fmt;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::error::{ErrorCode, MachiError};
macro_rules! typed_id {
($(#[$meta:meta])* $name:ident, $prefix:literal) => {
$(#[$meta])*
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct $name(String);
impl $name {
#[must_use]
pub fn generate() -> Self {
Self(format!("{}_{}", $prefix, Uuid::new_v4().simple()))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
pub fn new(value: impl Into<String>) -> Result<Self, MachiError> {
let value = value.into();
if value.trim().is_empty() {
return Err(MachiError::new(
ErrorCode::TypesInvalidId,
format!("{} must be non-empty", stringify!($name)),
));
}
Ok(Self(value))
}
}
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 = MachiError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s)
}
}
impl AsRef<str> for $name {
fn as_ref(&self) -> &str {
self.as_str()
}
}
};
}
typed_id!(
AgentId,
"agent"
);
typed_id!(
RunId,
"run"
);
typed_id!(
SessionId,
"session"
);
typed_id!(
ToolCallId,
"call"
);
typed_id!(
WorkflowRunId,
"wf"
);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn generate_has_prefix() {
let id = AgentId::generate();
assert!(id.as_str().starts_with("agent_"), "{}", id);
}
#[test]
fn rejects_empty() {
let err = SessionId::new(" ").expect_err("empty");
assert_eq!(err.code(), ErrorCode::TypesInvalidId);
}
}