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