a2a_protocol_server/agent_card/
static_handler.rs1use a2a_protocol_types::agent_card::AgentCard;
12use bytes::Bytes;
13use http_body_util::Full;
14
15use crate::agent_card::caching::{
16 check_conditional, format_http_date, make_etag, CacheConfig, ConditionalResult,
17};
18use crate::agent_card::CORS_ALLOW_ALL;
19use crate::error::ServerResult;
20
21#[derive(Debug, Clone)]
23pub struct StaticAgentCardHandler {
24 card_json: Bytes,
25 etag: String,
26 last_modified: String,
27 cache_config: CacheConfig,
28}
29
30impl StaticAgentCardHandler {
31 pub fn new(card: &AgentCard) -> ServerResult<Self> {
40 let json = serde_json::to_vec(card)?;
41 let etag = make_etag(&json);
42 Ok(Self {
43 card_json: Bytes::from(json),
44 etag,
45 last_modified: format_http_date(std::time::SystemTime::now()),
46 cache_config: CacheConfig::default(),
47 })
48 }
49
50 #[must_use]
52 pub const fn with_max_age(mut self, seconds: u32) -> Self {
53 self.cache_config = CacheConfig::with_max_age(seconds);
54 self
55 }
56
57 #[must_use]
62 pub fn handle(
63 &self,
64 req: &hyper::Request<impl hyper::body::Body>,
65 ) -> hyper::Response<Full<Bytes>> {
66 let result = check_conditional(req, &self.etag, &self.last_modified);
67 match result {
68 ConditionalResult::NotModified => self.not_modified_response(),
69 ConditionalResult::SendFull => self.full_response(),
70 }
71 }
72
73 #[must_use]
75 pub fn handle_unconditional(&self) -> hyper::Response<Full<Bytes>> {
76 self.full_response()
77 }
78
79 fn full_response(&self) -> hyper::Response<Full<Bytes>> {
80 hyper::Response::builder()
81 .status(200)
82 .header("content-type", "application/json")
83 .header("access-control-allow-origin", CORS_ALLOW_ALL)
84 .header("etag", &self.etag)
85 .header("last-modified", &self.last_modified)
86 .header("cache-control", self.cache_config.header_value())
87 .body(Full::new(self.card_json.clone()))
88 .unwrap_or_else(|_| hyper::Response::new(Full::new(Bytes::new())))
89 }
90
91 fn not_modified_response(&self) -> hyper::Response<Full<Bytes>> {
92 hyper::Response::builder()
93 .status(304)
94 .header("etag", &self.etag)
95 .header("last-modified", &self.last_modified)
96 .header("cache-control", self.cache_config.header_value())
97 .body(Full::new(Bytes::new()))
98 .unwrap_or_else(|_| hyper::Response::new(Full::new(Bytes::new())))
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use crate::agent_card::caching::tests::minimal_agent_card;
106
107 #[test]
108 fn static_handler_returns_etag_and_cache_headers() {
109 let card = minimal_agent_card();
110 let handler = StaticAgentCardHandler::new(&card).unwrap();
111 let req = hyper::Request::builder()
112 .method("GET")
113 .uri("/.well-known/agent.json")
114 .body(Full::new(Bytes::new()))
115 .unwrap();
116 let resp = handler.handle(&req);
117 assert_eq!(resp.status(), 200);
118 assert!(resp.headers().contains_key("etag"));
119 assert!(resp.headers().contains_key("last-modified"));
120 assert!(resp.headers().contains_key("cache-control"));
121 }
122
123 #[test]
124 fn static_handler_304_on_matching_etag() {
125 let card = minimal_agent_card();
126 let handler = StaticAgentCardHandler::new(&card).unwrap();
127 let req1 = hyper::Request::builder()
129 .method("GET")
130 .uri("/.well-known/agent.json")
131 .body(Full::new(Bytes::new()))
132 .unwrap();
133 let resp1 = handler.handle(&req1);
134 let etag = resp1
135 .headers()
136 .get("etag")
137 .unwrap()
138 .to_str()
139 .unwrap()
140 .to_owned();
141
142 let req2 = hyper::Request::builder()
144 .method("GET")
145 .uri("/.well-known/agent.json")
146 .header("if-none-match", &etag)
147 .body(Full::new(Bytes::new()))
148 .unwrap();
149 let resp2 = handler.handle(&req2);
150 assert_eq!(resp2.status(), 304);
151 }
152
153 #[test]
154 fn static_handler_200_on_mismatched_etag() {
155 let card = minimal_agent_card();
156 let handler = StaticAgentCardHandler::new(&card).unwrap();
157 let req = hyper::Request::builder()
158 .method("GET")
159 .uri("/.well-known/agent.json")
160 .header("if-none-match", "\"wrong-etag\"")
161 .body(Full::new(Bytes::new()))
162 .unwrap();
163 let resp = handler.handle(&req);
164 assert_eq!(resp.status(), 200);
165 }
166
167 #[test]
168 fn static_handler_custom_max_age() {
169 let card = minimal_agent_card();
170 let handler = StaticAgentCardHandler::new(&card)
171 .unwrap()
172 .with_max_age(7200);
173 let req = hyper::Request::builder()
174 .method("GET")
175 .uri("/.well-known/agent.json")
176 .body(Full::new(Bytes::new()))
177 .unwrap();
178 let resp = handler.handle(&req);
179 let cc = resp
180 .headers()
181 .get("cache-control")
182 .unwrap()
183 .to_str()
184 .unwrap();
185 assert!(cc.contains("max-age=7200"));
186 }
187}