use std::fmt;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use crate::domain::error::A2AError;
macro_rules! define_id {
($(#[$meta:meta])* $name:ident, $field:literal) => {
$(#[$meta])*
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct $name(String);
impl $name {
pub fn as_str(&self) -> &str {
&self.0
}
pub fn into_string(self) -> String {
self.0
}
}
impl FromStr for $name {
type Err = A2AError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.trim().is_empty() {
return Err(A2AError::ValidationError {
field: $field.to_string(),
message: concat!($field, " cannot be empty").to_string(),
});
}
Ok(Self(s.to_owned()))
}
}
impl TryFrom<&str> for $name {
type Error = A2AError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
s.parse()
}
}
impl TryFrom<String> for $name {
type Error = A2AError;
fn try_from(s: String) -> Result<Self, Self::Error> {
s.as_str().parse()
}
}
impl AsRef<str> for $name {
fn as_ref(&self) -> &str {
&self.0
}
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
};
}
define_id!(
TaskId,
"task_id"
);
define_id!(
ContextId,
"context_id"
);
define_id!(
PushConfigId,
"push_notification_config_id"
);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rejects_empty_and_whitespace() {
assert!(TaskId::from_str("").is_err());
assert!(TaskId::from_str(" ").is_err());
assert!(ContextId::from_str("").is_err());
}
#[test]
fn accepts_non_empty() {
let id = TaskId::from_str("task-123").unwrap();
assert_eq!(id.as_str(), "task-123");
assert_eq!(id.to_string(), "task-123");
}
#[test]
fn try_from_owned_and_borrowed() {
assert!(TaskId::try_from("x").is_ok());
assert!(TaskId::try_from("x".to_string()).is_ok());
assert!(TaskId::try_from(String::new()).is_err());
}
}