a2a_protocol_server/handler/lifecycle/
extended_card.rs1use std::collections::HashMap;
9use std::time::Instant;
10
11use a2a_protocol_types::agent_card::AgentCard;
12
13use crate::error::{ServerError, ServerResult};
14
15use super::super::helpers::build_call_context;
16use super::super::RequestHandler;
17
18impl RequestHandler {
19 pub async fn on_get_extended_agent_card(
25 &self,
26 headers: Option<&HashMap<String, String>>,
27 ) -> ServerResult<AgentCard> {
28 let start = Instant::now();
29 self.metrics.on_request("GetExtendedAgentCard");
30
31 let result: ServerResult<_> = async {
32 let call_ctx = build_call_context("GetExtendedAgentCard", headers);
33 self.interceptors.run_before(&call_ctx).await?;
34
35 let card = self
36 .agent_card
37 .clone()
38 .ok_or_else(|| ServerError::Internal("no agent card configured".into()))?;
39
40 self.interceptors.run_after(&call_ctx).await?;
41 Ok(card)
42 }
43 .await;
44
45 let elapsed = start.elapsed();
46 match &result {
47 Ok(_) => {
48 self.metrics.on_response("GetExtendedAgentCard");
49 self.metrics.on_latency("GetExtendedAgentCard", elapsed);
50 }
51 Err(e) => {
52 self.metrics
53 .on_error("GetExtendedAgentCard", &e.to_string());
54 self.metrics.on_latency("GetExtendedAgentCard", elapsed);
55 }
56 }
57 result
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use a2a_protocol_types::agent_card::{AgentCapabilities, AgentCard, AgentInterface};
64
65 use crate::agent_executor;
66 use crate::builder::RequestHandlerBuilder;
67 use crate::error::ServerError;
68
69 struct DummyExecutor;
70 agent_executor!(DummyExecutor, |_ctx, _queue| async { Ok(()) });
71
72 fn make_agent_card() -> AgentCard {
73 AgentCard {
74 url: None,
75 name: "Test Agent".into(),
76 description: "A test agent".into(),
77 version: "1.0.0".into(),
78 supported_interfaces: vec![AgentInterface {
79 url: "http://localhost:8080".into(),
80 protocol_binding: "JSONRPC".into(),
81 protocol_version: "1.0.0".into(),
82 tenant: None,
83 }],
84 default_input_modes: vec![],
85 default_output_modes: vec![],
86 skills: vec![],
87 capabilities: AgentCapabilities::none(),
88 provider: None,
89 icon_url: None,
90 documentation_url: None,
91 security_schemes: None,
92 security_requirements: None,
93 signatures: None,
94 }
95 }
96
97 #[tokio::test]
98 async fn get_extended_agent_card_no_card_returns_error() {
99 let handler = RequestHandlerBuilder::new(DummyExecutor).build().unwrap();
100 let result = handler.on_get_extended_agent_card(None).await;
101 assert!(
102 matches!(result, Err(ServerError::Internal(_))),
103 "expected Internal error when no agent card is configured, got: {result:?}"
104 );
105 }
106
107 #[tokio::test]
108 async fn get_extended_agent_card_with_card_returns_ok() {
109 let card = make_agent_card();
110 let handler = RequestHandlerBuilder::new(DummyExecutor)
111 .with_agent_card(card)
112 .build()
113 .unwrap();
114 let result = handler.on_get_extended_agent_card(None).await;
115 assert!(
116 result.is_ok(),
117 "expected Ok when agent card is configured, got: {result:?}"
118 );
119 assert_eq!(result.unwrap().name, "Test Agent");
120 }
121
122 #[tokio::test]
123 async fn get_extended_agent_card_error_path_records_metrics() {
124 let handler = RequestHandlerBuilder::new(DummyExecutor).build().unwrap();
126 let result = handler.on_get_extended_agent_card(None).await;
127 assert!(
128 matches!(result, Err(ServerError::Internal(_))),
129 "expected Internal error for error metrics path, got: {result:?}"
130 );
131 }
132}