a2a_rs/port/
notification_manager.rs1#[cfg(feature = "server")]
4use async_trait::async_trait;
5
6use crate::domain::{A2AError, PushNotificationConfig, TaskIdParams, TaskPushNotificationConfig};
7
8fn validate_push_notification_url(config: &PushNotificationConfig) -> Result<(), A2AError> {
13 if config.url.trim().is_empty() {
14 return Err(A2AError::ValidationError {
15 field: "url".to_string(),
16 message: "Webhook URL cannot be empty".to_string(),
17 });
18 }
19
20 match url::Url::parse(&config.url) {
21 Ok(parsed_url) => {
22 let scheme = parsed_url.scheme();
23 if scheme != "https" {
24 let is_localhost = parsed_url
25 .host_str()
26 .map(|h| h == "localhost" || h == "127.0.0.1" || h == "::1")
27 .unwrap_or(false);
28
29 if scheme != "http" || !is_localhost {
30 return Err(A2AError::ValidationError {
31 field: "url".to_string(),
32 message: "Webhook URL must use HTTPS (HTTP is only allowed for localhost)"
33 .to_string(),
34 });
35 }
36 }
37 }
38 Err(_) => {
39 return Err(A2AError::ValidationError {
40 field: "url".to_string(),
41 message: "Invalid webhook URL format".to_string(),
42 });
43 }
44 }
45
46 Ok(())
47}
48
49pub trait NotificationManager {
51 fn set_task_notification(
53 &self,
54 config: &TaskPushNotificationConfig,
55 ) -> Result<TaskPushNotificationConfig, A2AError>;
56
57 fn get_task_notification(&self, task_id: &str) -> Result<TaskPushNotificationConfig, A2AError>;
59
60 fn remove_task_notification(&self, task_id: &str) -> Result<(), A2AError>;
62
63 fn has_task_notification(&self, task_id: &str) -> Result<bool, A2AError> {
65 match self.get_task_notification(task_id) {
66 Ok(_) => Ok(true),
67 Err(A2AError::TaskNotFound(_)) => Ok(false),
68 Err(e) => Err(e),
69 }
70 }
71
72 fn validate_notification_config(
74 &self,
75 config: &PushNotificationConfig,
76 ) -> Result<(), A2AError> {
77 validate_push_notification_url(config)
78 }
79
80 fn send_test_notification(&self, config: &PushNotificationConfig) -> Result<(), A2AError> {
82 self.validate_notification_config(config)?;
84 Ok(())
86 }
87}
88
89#[cfg(feature = "server")]
90#[async_trait]
91pub trait AsyncNotificationManager: Send + Sync {
93 async fn set_task_notification(
95 &self,
96 config: &TaskPushNotificationConfig,
97 ) -> Result<TaskPushNotificationConfig, A2AError>;
98
99 async fn get_task_notification(
101 &self,
102 task_id: &str,
103 ) -> Result<TaskPushNotificationConfig, A2AError>;
104
105 async fn remove_task_notification(&self, task_id: &str) -> Result<(), A2AError>;
107
108 async fn has_task_notification(&self, task_id: &str) -> Result<bool, A2AError> {
110 match self.get_task_notification(task_id).await {
111 Ok(_) => Ok(true),
112 Err(A2AError::TaskNotFound(_)) => Ok(false),
113 Err(e) => Err(e),
114 }
115 }
116
117 async fn validate_notification_config(
119 &self,
120 config: &PushNotificationConfig,
121 ) -> Result<(), A2AError> {
122 validate_push_notification_url(config)
123 }
124
125 async fn send_test_notification(
127 &self,
128 config: &PushNotificationConfig,
129 ) -> Result<(), A2AError> {
130 self.validate_notification_config(config).await?;
132 Ok(())
134 }
135
136 async fn set_task_notification_validated(
138 &self,
139 config: &TaskPushNotificationConfig,
140 ) -> Result<TaskPushNotificationConfig, A2AError> {
141 if config.task_id.trim().is_empty() {
143 return Err(A2AError::ValidationError {
144 field: "task_id".to_string(),
145 message: "Task ID cannot be empty".to_string(),
146 });
147 }
148
149 self.validate_notification_config(&config.push_notification_config)
151 .await?;
152
153 self.set_task_notification(config).await
155 }
156
157 async fn get_task_notification_validated(
159 &self,
160 params: &TaskIdParams,
161 ) -> Result<TaskPushNotificationConfig, A2AError> {
162 if params.id.trim().is_empty() {
163 return Err(A2AError::ValidationError {
164 field: "task_id".to_string(),
165 message: "Task ID cannot be empty".to_string(),
166 });
167 }
168
169 self.get_task_notification(¶ms.id).await
170 }
171
172 async fn notify_task_status_update(
174 &self,
175 task_id: &str,
176 _status_update: &crate::domain::TaskStatusUpdateEvent,
177 ) -> Result<(), A2AError> {
178 if !self.has_task_notification(task_id).await? {
181 return Ok(()); }
183
184 let _config = self.get_task_notification(task_id).await?;
187
188 Ok(())
189 }
190
191 async fn notify_task_artifact_update(
193 &self,
194 task_id: &str,
195 _artifact_update: &crate::domain::TaskArtifactUpdateEvent,
196 ) -> Result<(), A2AError> {
197 if !self.has_task_notification(task_id).await? {
200 return Ok(()); }
202
203 let _config = self.get_task_notification(task_id).await?;
206
207 Ok(())
208 }
209}