1use std::fmt;
19use std::str::FromStr;
20
21use serde::{Deserialize, Serialize};
22
23use crate::domain::error::A2AError;
24
25macro_rules! define_id {
27 ($(#[$meta:meta])* $name:ident, $field:literal) => {
28 $(#[$meta])*
29 #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
30 #[serde(transparent)]
31 pub struct $name(String);
32
33 impl $name {
34 pub fn as_str(&self) -> &str {
36 &self.0
37 }
38
39 pub fn into_string(self) -> String {
41 self.0
42 }
43 }
44
45 impl FromStr for $name {
46 type Err = A2AError;
47
48 fn from_str(s: &str) -> Result<Self, Self::Err> {
49 if s.trim().is_empty() {
50 return Err(A2AError::ValidationError {
51 field: $field.to_string(),
52 message: concat!($field, " cannot be empty").to_string(),
53 });
54 }
55 Ok(Self(s.to_owned()))
56 }
57 }
58
59 impl TryFrom<&str> for $name {
60 type Error = A2AError;
61
62 fn try_from(s: &str) -> Result<Self, Self::Error> {
63 s.parse()
64 }
65 }
66
67 impl TryFrom<String> for $name {
68 type Error = A2AError;
69
70 fn try_from(s: String) -> Result<Self, Self::Error> {
71 s.as_str().parse()
72 }
73 }
74
75 impl AsRef<str> for $name {
76 fn as_ref(&self) -> &str {
77 &self.0
78 }
79 }
80
81 impl fmt::Display for $name {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 f.write_str(&self.0)
84 }
85 }
86 };
87}
88
89define_id!(
90 TaskId,
92 "task_id"
93);
94
95define_id!(
96 ContextId,
98 "context_id"
99);
100
101define_id!(
102 PushConfigId,
104 "push_notification_config_id"
105);
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn rejects_empty_and_whitespace() {
113 assert!(TaskId::from_str("").is_err());
114 assert!(TaskId::from_str(" ").is_err());
115 assert!(ContextId::from_str("").is_err());
116 }
117
118 #[test]
119 fn accepts_non_empty() {
120 let id = TaskId::from_str("task-123").unwrap();
121 assert_eq!(id.as_str(), "task-123");
122 assert_eq!(id.to_string(), "task-123");
123 }
124
125 #[test]
126 fn try_from_owned_and_borrowed() {
127 assert!(TaskId::try_from("x").is_ok());
128 assert!(TaskId::try_from("x".to_string()).is_ok());
129 assert!(TaskId::try_from(String::new()).is_err());
130 }
131}