a2a_protocol_server/handler/lifecycle/
get_task.rs1use std::collections::HashMap;
9use std::time::Instant;
10
11use a2a_protocol_types::params::TaskQueryParams;
12use a2a_protocol_types::task::{Task, TaskId};
13
14use crate::error::{ServerError, ServerResult};
15
16use super::super::helpers::build_call_context;
17use super::super::RequestHandler;
18
19impl RequestHandler {
20 pub async fn on_get_task(
26 &self,
27 params: TaskQueryParams,
28 headers: Option<&HashMap<String, String>>,
29 ) -> ServerResult<Task> {
30 let start = Instant::now();
31 trace_info!(method = "GetTask", task_id = %params.id, "handling get task");
32 self.metrics.on_request("GetTask");
33
34 let tenant = params.tenant.clone().unwrap_or_default();
35 let result: ServerResult<_> = crate::store::tenant::TenantContext::scope(tenant, async {
36 let call_ctx = build_call_context("GetTask", headers);
37 self.interceptors.run_before(&call_ctx).await?;
38
39 let task_id = TaskId::new(¶ms.id);
40 let task = self
41 .task_store
42 .get(&task_id)
43 .await?
44 .ok_or_else(|| ServerError::TaskNotFound(task_id))?;
45
46 self.interceptors.run_after(&call_ctx).await?;
47 Ok(task)
48 })
49 .await;
50
51 let elapsed = start.elapsed();
52 match &result {
53 Ok(_) => {
54 self.metrics.on_response("GetTask");
55 self.metrics.on_latency("GetTask", elapsed);
56 }
57 Err(e) => {
58 self.metrics.on_error("GetTask", &e.to_string());
59 self.metrics.on_latency("GetTask", elapsed);
60 }
61 }
62 result
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use a2a_protocol_types::params::TaskQueryParams;
69 use a2a_protocol_types::task::{ContextId, Task, TaskId, TaskState, TaskStatus};
70
71 use crate::agent_executor;
72 use crate::builder::RequestHandlerBuilder;
73 use crate::error::ServerError;
74
75 struct DummyExecutor;
76 agent_executor!(DummyExecutor, |_ctx, _queue| async { Ok(()) });
77
78 fn make_completed_task(id: &str) -> Task {
79 Task {
80 id: TaskId::new(id),
81 context_id: ContextId::new("ctx-1"),
82 status: TaskStatus::new(TaskState::Completed),
83 history: None,
84 artifacts: None,
85 metadata: None,
86 }
87 }
88
89 #[tokio::test]
90 async fn get_task_not_found_returns_error() {
91 let handler = RequestHandlerBuilder::new(DummyExecutor).build().unwrap();
92 let params = TaskQueryParams {
93 tenant: None,
94 id: "nonexistent-task".to_owned(),
95 history_length: None,
96 };
97 let result = handler.on_get_task(params, None).await;
98 assert!(
99 matches!(result, Err(ServerError::TaskNotFound(_))),
100 "expected TaskNotFound for missing task, got: {result:?}"
101 );
102 }
103
104 #[tokio::test]
105 async fn get_task_found_returns_task() {
106 let handler = RequestHandlerBuilder::new(DummyExecutor).build().unwrap();
107 let task = make_completed_task("t-get-1");
108 handler.task_store.save(task).await.unwrap();
109
110 let params = TaskQueryParams {
111 tenant: None,
112 id: "t-get-1".to_owned(),
113 history_length: None,
114 };
115 let result = handler.on_get_task(params, None).await;
116 assert!(
117 result.is_ok(),
118 "expected Ok for existing task, got: {result:?}"
119 );
120 assert_eq!(result.unwrap().id, TaskId::new("t-get-1"));
121 }
122
123 #[tokio::test]
124 async fn get_task_error_path_records_metrics() {
125 let handler = RequestHandlerBuilder::new(DummyExecutor).build().unwrap();
127 let params = TaskQueryParams {
128 tenant: None,
129 id: "nonexistent-metrics".to_owned(),
130 history_length: None,
131 };
132 let result = handler.on_get_task(params, None).await;
133 assert!(
134 matches!(result, Err(ServerError::TaskNotFound(_))),
135 "expected TaskNotFound for error metrics path, got: {result:?}"
136 );
137 }
138}