protocol_droid/domain/a2a/
push_notification_methods.rs1use std::collections::BTreeMap;
63
64use serde::{Deserialize, Serialize};
65
66use super::push_notification::TaskPushNotificationConfig;
67
68#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
75#[serde(transparent)]
76pub struct CreateTaskPushNotificationConfigRequest(pub TaskPushNotificationConfig);
77
78impl From<TaskPushNotificationConfig> for CreateTaskPushNotificationConfigRequest {
79 fn from(config: TaskPushNotificationConfig) -> Self {
80 Self(config)
81 }
82}
83
84impl std::ops::Deref for CreateTaskPushNotificationConfigRequest {
85 type Target = TaskPushNotificationConfig;
86
87 fn deref(&self) -> &Self::Target {
88 &self.0
89 }
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
97#[serde(transparent)]
98pub struct CreateTaskPushNotificationConfigResponse(pub TaskPushNotificationConfig);
99
100impl From<TaskPushNotificationConfig> for CreateTaskPushNotificationConfigResponse {
101 fn from(config: TaskPushNotificationConfig) -> Self {
102 Self(config)
103 }
104}
105
106impl std::ops::Deref for CreateTaskPushNotificationConfigResponse {
107 type Target = TaskPushNotificationConfig;
108
109 fn deref(&self) -> &Self::Target {
110 &self.0
111 }
112}
113
114#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
124#[serde(rename_all = "camelCase")]
125pub struct GetTaskPushNotificationConfigRequest {
126 pub id: String,
128 pub task_id: String,
130}
131
132impl GetTaskPushNotificationConfigRequest {
133 #[must_use]
136 pub fn new(id: impl Into<String>, task_id: impl Into<String>) -> Self {
137 Self {
138 id: id.into(),
139 task_id: task_id.into(),
140 }
141 }
142}
143
144#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
149#[serde(transparent)]
150pub struct GetTaskPushNotificationConfigResponse(pub TaskPushNotificationConfig);
151
152impl From<TaskPushNotificationConfig> for GetTaskPushNotificationConfigResponse {
153 fn from(config: TaskPushNotificationConfig) -> Self {
154 Self(config)
155 }
156}
157
158impl std::ops::Deref for GetTaskPushNotificationConfigResponse {
159 type Target = TaskPushNotificationConfig;
160
161 fn deref(&self) -> &Self::Target {
162 &self.0
163 }
164}
165
166#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
181#[serde(rename_all = "camelCase")]
182pub struct ListTaskPushNotificationConfigsRequest {
183 pub task_id: String,
185 #[serde(default, skip_serializing_if = "Option::is_none")]
189 pub page_size: Option<u32>,
190 #[serde(default, skip_serializing_if = "Option::is_none")]
196 pub page_token: Option<String>,
197}
198
199impl ListTaskPushNotificationConfigsRequest {
200 #[must_use]
206 pub fn new(task_id: impl Into<String>) -> Self {
207 Self {
208 task_id: task_id.into(),
209 page_size: None,
210 page_token: None,
211 }
212 }
213
214 #[must_use]
216 pub const fn with_page_size(mut self, page_size: u32) -> Self {
217 self.page_size = Some(page_size);
218 self
219 }
220
221 #[must_use]
223 pub fn with_page_token(mut self, page_token: impl Into<String>) -> Self {
224 self.page_token = Some(page_token.into());
225 self
226 }
227}
228
229#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
238#[serde(rename_all = "camelCase")]
239pub struct ListTaskPushNotificationConfigsResponse {
240 #[serde(default, skip_serializing_if = "Vec::is_empty")]
243 pub configs: Vec<TaskPushNotificationConfig>,
244 #[serde(default, skip_serializing_if = "Option::is_none")]
250 pub next_page_token: Option<String>,
251}
252
253impl ListTaskPushNotificationConfigsResponse {
254 #[must_use]
259 pub fn new(configs: impl IntoIterator<Item = TaskPushNotificationConfig>) -> Self {
260 Self {
261 configs: configs.into_iter().collect(),
262 next_page_token: None,
263 }
264 }
265
266 #[must_use]
268 pub fn with_next_page_token(mut self, next_page_token: impl Into<String>) -> Self {
269 self.next_page_token = Some(next_page_token.into());
270 self
271 }
272}
273
274#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
284#[serde(rename_all = "camelCase")]
285pub struct DeleteTaskPushNotificationConfigRequest {
286 pub id: String,
288 pub task_id: String,
290}
291
292impl DeleteTaskPushNotificationConfigRequest {
293 #[must_use]
296 pub fn new(id: impl Into<String>, task_id: impl Into<String>) -> Self {
297 Self {
298 id: id.into(),
299 task_id: task_id.into(),
300 }
301 }
302}
303
304#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
308pub struct DeleteTaskPushNotificationConfigResponse {
309 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
316 pub metadata: BTreeMap<String, serde_json::Value>,
317}
318
319#[cfg(test)]
320mod tests {
321 use super::{
322 CreateTaskPushNotificationConfigRequest, CreateTaskPushNotificationConfigResponse,
323 DeleteTaskPushNotificationConfigRequest, DeleteTaskPushNotificationConfigResponse,
324 GetTaskPushNotificationConfigRequest, GetTaskPushNotificationConfigResponse,
325 ListTaskPushNotificationConfigsRequest, ListTaskPushNotificationConfigsResponse,
326 };
327 use crate::domain::a2a::push_notification::TaskPushNotificationConfig;
328
329 type TestError = Box<dyn std::error::Error>;
330
331 fn sample_config() -> TaskPushNotificationConfig {
332 TaskPushNotificationConfig::new("c-1", "t-1", "https://example.com/wh")
333 }
334
335 #[test]
338 fn create_request_serialises_as_config_object() -> Result<(), TestError> {
339 let request = CreateTaskPushNotificationConfigRequest::from(sample_config());
340 let value = serde_json::to_value(&request)?;
341 let obj = value
342 .as_object()
343 .ok_or("a CreateTaskPushNotificationConfigRequest must serialise to a JSON object")?;
344 assert_eq!(
345 obj.get("id").and_then(serde_json::Value::as_str),
346 Some("c-1")
347 );
348 assert_eq!(
349 obj.get("taskId").and_then(serde_json::Value::as_str),
350 Some("t-1")
351 );
352 assert!(!obj.contains_key("config"));
355 Ok(())
356 }
357
358 #[test]
359 fn create_response_from_config_via_into_works() {
360 let config = sample_config();
361 let response: CreateTaskPushNotificationConfigResponse = config.clone().into();
362 assert_eq!(response.0, config);
363 }
364
365 #[test]
368 fn get_request_serialises_with_id_and_task_id() -> Result<(), TestError> {
369 let request = GetTaskPushNotificationConfigRequest::new("c-1", "t-1");
370 let value = serde_json::to_value(&request)?;
371 let obj = value
372 .as_object()
373 .ok_or("a GetTaskPushNotificationConfigRequest must serialise to a JSON object")?;
374 assert_eq!(
375 obj.get("id").and_then(serde_json::Value::as_str),
376 Some("c-1")
377 );
378 assert_eq!(
379 obj.get("taskId").and_then(serde_json::Value::as_str),
380 Some("t-1")
381 );
382 Ok(())
383 }
384
385 #[test]
386 fn get_response_serialises_as_config_object() -> Result<(), TestError> {
387 let response = GetTaskPushNotificationConfigResponse::from(sample_config());
388 let value = serde_json::to_value(&response)?;
389 let obj = value
390 .as_object()
391 .ok_or("a GetTaskPushNotificationConfigResponse must serialise to a JSON object")?;
392 assert_eq!(
393 obj.get("id").and_then(serde_json::Value::as_str),
394 Some("c-1")
395 );
396 Ok(())
397 }
398
399 #[test]
402 fn list_request_serialises_with_task_id_only() -> Result<(), TestError> {
403 let request = ListTaskPushNotificationConfigsRequest::new("t-1");
404 let value = serde_json::to_value(&request)?;
405 let obj = value
406 .as_object()
407 .ok_or("a ListTaskPushNotificationConfigsRequest must serialise to a JSON object")?;
408 assert_eq!(
409 obj.get("taskId").and_then(serde_json::Value::as_str),
410 Some("t-1")
411 );
412 assert!(!obj.contains_key("pageSize"));
413 assert!(!obj.contains_key("pageToken"));
414 Ok(())
415 }
416
417 #[test]
418 fn list_request_round_trips_with_pagination() -> Result<(), TestError> {
419 let request = ListTaskPushNotificationConfigsRequest::new("t-1")
420 .with_page_size(50)
421 .with_page_token("next-page");
422 let json = serde_json::to_string(&request)?;
423 let parsed: ListTaskPushNotificationConfigsRequest = serde_json::from_str(&json)?;
424 assert_eq!(parsed, request);
425 Ok(())
426 }
427
428 #[test]
429 fn list_response_with_no_next_page_token_omits_the_field() -> Result<(), TestError> {
430 let response = ListTaskPushNotificationConfigsResponse::new([sample_config()]);
434 let value = serde_json::to_value(&response)?;
435 let obj = value
436 .as_object()
437 .ok_or("a ListTaskPushNotificationConfigsResponse must serialise to a JSON object")?;
438 assert!(!obj.contains_key("nextPageToken"));
439 let configs = obj
440 .get("configs")
441 .and_then(serde_json::Value::as_array)
442 .ok_or("`configs` must be a JSON array")?;
443 assert_eq!(configs.len(), 1);
444 Ok(())
445 }
446
447 #[test]
448 fn list_response_round_trips_with_token_and_configs() -> Result<(), TestError> {
449 let response = ListTaskPushNotificationConfigsResponse::new([
450 sample_config(),
451 TaskPushNotificationConfig::new("c-2", "t-1", "https://example.com/wh2"),
452 ])
453 .with_next_page_token("next");
454 let json = serde_json::to_string(&response)?;
455 let parsed: ListTaskPushNotificationConfigsResponse = serde_json::from_str(&json)?;
456 assert_eq!(parsed, response);
457 Ok(())
458 }
459
460 #[test]
463 fn delete_request_serialises_with_id_and_task_id() -> Result<(), TestError> {
464 let request = DeleteTaskPushNotificationConfigRequest::new("c-1", "t-1");
465 let value = serde_json::to_value(&request)?;
466 let obj = value
467 .as_object()
468 .ok_or("a DeleteTaskPushNotificationConfigRequest must serialise to a JSON object")?;
469 assert_eq!(
470 obj.get("id").and_then(serde_json::Value::as_str),
471 Some("c-1")
472 );
473 assert_eq!(
474 obj.get("taskId").and_then(serde_json::Value::as_str),
475 Some("t-1")
476 );
477 Ok(())
478 }
479
480 #[test]
481 fn delete_response_with_no_metadata_is_an_empty_object() -> Result<(), TestError> {
482 let response = DeleteTaskPushNotificationConfigResponse::default();
489 let value = serde_json::to_value(&response)?;
490 let obj = value
491 .as_object()
492 .ok_or("a DeleteTaskPushNotificationConfigResponse must serialise to a JSON object")?;
493 assert!(obj.is_empty(), "expected `{{}}`, got {value}");
494 Ok(())
495 }
496}