a2a_protocol_client/methods/
extended_card.rs1use a2a_protocol_types::AuthenticatedExtendedCardResponse;
9
10use crate::client::A2aClient;
11use crate::error::{ClientError, ClientResult};
12use crate::interceptor::{ClientRequest, ClientResponse};
13
14impl A2aClient {
15 pub async fn get_extended_agent_card(&self) -> ClientResult<AuthenticatedExtendedCardResponse> {
29 const METHOD: &str = "GetExtendedAgentCard";
30
31 let params = self.tenant_or_default(None).map_or(
35 serde_json::Value::Null,
36 |tenant| serde_json::json!({ "tenant": tenant }),
37 );
38 let mut req = ClientRequest::new(METHOD, params);
39 self.interceptors.run_before(&mut req).await?;
40
41 let result = self.send_intercepted(METHOD, &mut req).await?;
42
43 let resp = ClientResponse {
44 method: METHOD.to_owned(),
45 result,
46 status_code: 200,
47 };
48 self.interceptors.run_after(&resp).await?;
49
50 serde_json::from_value::<AuthenticatedExtendedCardResponse>(resp.result)
51 .map_err(ClientError::Serialization)
52 }
53}
54
55#[cfg(test)]
58mod tests {
59 use std::collections::HashMap;
60 use std::future::Future;
61 use std::pin::Pin;
62
63 use crate::ClientBuilder;
64 use crate::error::{ClientError, ClientResult};
65 use crate::streaming::EventStream;
66 use crate::transport::Transport;
67
68 struct MockTransport {
69 response: serde_json::Value,
70 }
71
72 impl MockTransport {
73 fn new(response: serde_json::Value) -> Self {
74 Self { response }
75 }
76 }
77
78 impl Transport for MockTransport {
79 fn send_request<'a>(
80 &'a self,
81 _method: &'a str,
82 _params: serde_json::Value,
83 _extra_headers: &'a HashMap<String, String>,
84 ) -> Pin<Box<dyn Future<Output = ClientResult<serde_json::Value>> + Send + 'a>> {
85 let resp = self.response.clone();
86 Box::pin(async move { Ok(resp) })
87 }
88
89 fn send_streaming_request<'a>(
90 &'a self,
91 _method: &'a str,
92 _params: serde_json::Value,
93 _extra_headers: &'a HashMap<String, String>,
94 ) -> Pin<Box<dyn Future<Output = ClientResult<EventStream>> + Send + 'a>> {
95 Box::pin(async move { Err(ClientError::Transport("not supported".into())) })
96 }
97 }
98
99 struct ErrorTransport {
100 error_msg: String,
101 }
102
103 impl Transport for ErrorTransport {
104 fn send_request<'a>(
105 &'a self,
106 _method: &'a str,
107 _params: serde_json::Value,
108 _extra_headers: &'a HashMap<String, String>,
109 ) -> Pin<Box<dyn Future<Output = ClientResult<serde_json::Value>> + Send + 'a>> {
110 let msg = self.error_msg.clone();
111 Box::pin(async move { Err(ClientError::Transport(msg)) })
112 }
113
114 fn send_streaming_request<'a>(
115 &'a self,
116 _method: &'a str,
117 _params: serde_json::Value,
118 _extra_headers: &'a HashMap<String, String>,
119 ) -> Pin<Box<dyn Future<Output = ClientResult<EventStream>> + Send + 'a>> {
120 let msg = self.error_msg.clone();
121 Box::pin(async move { Err(ClientError::Transport(msg)) })
122 }
123 }
124
125 fn make_client(transport: impl Transport) -> crate::A2aClient {
126 ClientBuilder::new("http://localhost:8080")
127 .with_custom_transport(transport)
128 .build()
129 .expect("build client")
130 }
131
132 fn agent_card_json() -> serde_json::Value {
133 serde_json::json!({
134 "name": "test-agent",
135 "description": "A test agent",
136 "version": "1.0.0",
137 "supportedInterfaces": [{
138 "url": "http://localhost:8080",
139 "protocolBinding": "JSONRPC",
140 "protocolVersion": "1.0.0"
141 }],
142 "defaultInputModes": ["text/plain"],
143 "defaultOutputModes": ["text/plain"],
144 "skills": [{
145 "id": "echo",
146 "name": "Echo",
147 "description": "Echoes input",
148 "tags": ["test"]
149 }],
150 "capabilities": {}
151 })
152 }
153
154 #[tokio::test]
155 async fn get_extended_agent_card_success() {
156 let transport = MockTransport::new(agent_card_json());
157 let client = make_client(transport);
158
159 let card = client.get_extended_agent_card().await.unwrap();
160 assert_eq!(card.name, "test-agent");
161 assert_eq!(card.version, "1.0.0");
162 assert_eq!(card.skills.len(), 1);
163 assert_eq!(card.skills[0].id, "echo");
164 }
165
166 #[tokio::test]
167 async fn get_extended_agent_card_transport_error() {
168 let transport = ErrorTransport {
169 error_msg: "connection refused".into(),
170 };
171 let client = make_client(transport);
172
173 let err = client.get_extended_agent_card().await.unwrap_err();
174 assert!(
175 matches!(err, ClientError::Transport(ref msg) if msg.contains("connection refused")),
176 "expected Transport error, got {err:?}"
177 );
178 }
179
180 #[tokio::test]
182 async fn mock_transport_streaming_returns_not_supported() {
183 let transport = MockTransport::new(serde_json::json!({}));
184 let client = make_client(transport);
185
186 let err = client.subscribe_to_task("task-1").await.unwrap_err();
187 assert!(
188 matches!(err, ClientError::Transport(ref msg) if msg.contains("not supported")),
189 "expected Transport error from MockTransport streaming, got {err:?}"
190 );
191 }
192
193 #[tokio::test]
195 async fn error_transport_streaming_returns_error() {
196 let transport = ErrorTransport {
197 error_msg: "stream refused".into(),
198 };
199 let client = make_client(transport);
200
201 let err = client.subscribe_to_task("task-2").await.unwrap_err();
202 assert!(
203 matches!(err, ClientError::Transport(ref msg) if msg.contains("stream refused")),
204 "expected Transport error from ErrorTransport streaming, got {err:?}"
205 );
206 }
207}