a2a_agents/registry/
card_source.rs1use std::collections::HashMap;
15use std::sync::Arc;
16
17use async_trait::async_trait;
18use thiserror::Error;
19use tokio::sync::RwLock;
20
21use a2a_rs::domain::AgentCard;
22
23#[derive(Debug, Error)]
29#[error("could not read the agent card at {endpoint}: {reason}")]
30pub struct CardSourceError {
31 pub endpoint: String,
33 pub reason: String,
35}
36
37#[async_trait]
39pub trait CardSource: Send + Sync {
40 async fn fetch(&self, endpoint: &str) -> Result<AgentCard, CardSourceError>;
42}
43
44#[derive(Debug, Clone, Copy, Default)]
46pub struct HttpCardSource;
47
48impl HttpCardSource {
49 pub fn new() -> Self {
51 Self
52 }
53}
54
55#[async_trait]
56impl CardSource for HttpCardSource {
57 async fn fetch(&self, endpoint: &str) -> Result<AgentCard, CardSourceError> {
58 a2a_rs::fetch_agent_card(endpoint)
59 .await
60 .map_err(|e| CardSourceError {
61 endpoint: endpoint.to_string(),
62 reason: e.to_string(),
63 })
64 }
65}
66
67#[derive(Clone, Default)]
73pub struct InMemoryCardSource {
74 cards: Arc<RwLock<HashMap<String, AgentCard>>>,
75}
76
77impl InMemoryCardSource {
78 pub fn new() -> Self {
80 Self::default()
81 }
82
83 pub async fn insert(&self, endpoint: impl Into<String>, card: AgentCard) {
85 self.cards.write().await.insert(endpoint.into(), card);
86 }
87}
88
89#[async_trait]
90impl CardSource for InMemoryCardSource {
91 async fn fetch(&self, endpoint: &str) -> Result<AgentCard, CardSourceError> {
92 self.cards
93 .read()
94 .await
95 .get(endpoint)
96 .cloned()
97 .ok_or_else(|| CardSourceError {
98 endpoint: endpoint.to_string(),
99 reason: "no card registered for this endpoint".to_string(),
100 })
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[tokio::test]
109 async fn in_memory_source_answers_only_for_known_endpoints() {
110 let source = InMemoryCardSource::new();
111 let card = AgentCard {
112 name: "Weather Agent".to_string(),
113 ..Default::default()
114 };
115 source.insert("http://127.0.0.1:8080", card).await;
116
117 assert_eq!(
118 source.fetch("http://127.0.0.1:8080").await.unwrap().name,
119 "Weather Agent"
120 );
121
122 let err = source.fetch("http://127.0.0.1:9999").await.unwrap_err();
125 assert_eq!(err.endpoint, "http://127.0.0.1:9999");
126 assert!(err.to_string().contains("http://127.0.0.1:9999"));
127 }
128}