a2a_protocol_server/dispatch/
mod.rs1#[cfg(feature = "axum")]
9pub mod axum_adapter;
10pub mod cors;
11#[cfg(feature = "grpc")]
12pub mod grpc;
13pub mod jsonrpc;
14pub mod rest;
15#[cfg(feature = "websocket")]
16pub mod websocket;
17
18pub use cors::CorsConfig;
19#[cfg(feature = "grpc")]
20pub use grpc::{GrpcConfig, GrpcDispatcher};
21pub use jsonrpc::JsonRpcDispatcher;
22pub use rest::RestDispatcher;
23#[cfg(feature = "websocket")]
24pub use websocket::WebSocketDispatcher;
25
26#[derive(Debug, Clone)]
42pub struct DispatchConfig {
43 pub max_request_body_size: usize,
45 pub body_read_timeout: std::time::Duration,
47 pub max_query_string_length: usize,
49 pub sse_keep_alive_interval: std::time::Duration,
54 pub sse_channel_capacity: usize,
59 pub max_batch_size: usize,
64 pub require_version_header: bool,
74}
75
76impl Default for DispatchConfig {
77 fn default() -> Self {
78 Self {
79 max_request_body_size: 4 * 1024 * 1024,
80 body_read_timeout: std::time::Duration::from_secs(30),
81 max_query_string_length: 4096,
82 sse_keep_alive_interval: std::time::Duration::from_secs(30),
83 sse_channel_capacity: 64,
84 max_batch_size: 100,
85 require_version_header: true,
86 }
87 }
88}
89
90impl DispatchConfig {
91 #[must_use]
93 pub const fn with_max_request_body_size(mut self, size: usize) -> Self {
94 self.max_request_body_size = size;
95 self
96 }
97
98 #[must_use]
100 pub const fn with_body_read_timeout(mut self, timeout: std::time::Duration) -> Self {
101 self.body_read_timeout = timeout;
102 self
103 }
104
105 #[must_use]
107 pub const fn with_max_query_string_length(mut self, length: usize) -> Self {
108 self.max_query_string_length = length;
109 self
110 }
111
112 #[must_use]
114 pub const fn with_sse_keep_alive_interval(mut self, interval: std::time::Duration) -> Self {
115 self.sse_keep_alive_interval = interval;
116 self
117 }
118
119 #[must_use]
121 pub const fn with_sse_channel_capacity(mut self, capacity: usize) -> Self {
122 self.sse_channel_capacity = capacity;
123 self
124 }
125
126 #[must_use]
128 pub const fn with_max_batch_size(mut self, size: usize) -> Self {
129 self.max_batch_size = size;
130 self
131 }
132
133 #[must_use]
141 pub const fn accept_missing_version_header(mut self) -> Self {
142 self.require_version_header = false;
143 self
144 }
145}
146
147pub(crate) fn validate_version_header(
156 value: Option<&str>,
157 require: bool,
158) -> Result<(), a2a_protocol_types::error::A2aError> {
159 let v = value.unwrap_or("").trim();
160 if v.is_empty() {
161 if require {
162 return Err(a2a_protocol_types::error::A2aError::version_not_supported(
163 "A2A version '0.3' is not supported by this server; expected '1.0' (send the A2A-Version header)",
164 ));
165 }
166 return Ok(());
167 }
168 let major = v.split('.').next().and_then(|s| s.parse::<u32>().ok());
169 if major == Some(1) {
170 return Ok(());
171 }
172 Err(a2a_protocol_types::error::A2aError::version_not_supported(
173 format!("unsupported A2A version: {v}; this server supports 1.x"),
174 ))
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180 use std::time::Duration;
181
182 #[test]
183 fn default_values() {
184 let config = DispatchConfig::default();
185 assert_eq!(config.max_request_body_size, 4 * 1024 * 1024);
186 assert_eq!(config.body_read_timeout, Duration::from_secs(30));
187 assert_eq!(config.max_query_string_length, 4096);
188 assert_eq!(config.sse_keep_alive_interval, Duration::from_secs(30));
189 assert_eq!(config.sse_channel_capacity, 64);
190 assert_eq!(config.max_batch_size, 100);
191 }
192
193 #[test]
194 fn with_max_request_body_size_sets_value() {
195 let config = DispatchConfig::default().with_max_request_body_size(8 * 1024 * 1024);
196 assert_eq!(config.max_request_body_size, 8 * 1024 * 1024);
197 }
198
199 #[test]
200 fn with_body_read_timeout_sets_value() {
201 let config = DispatchConfig::default().with_body_read_timeout(Duration::from_secs(60));
202 assert_eq!(config.body_read_timeout, Duration::from_secs(60));
203 }
204
205 #[test]
206 fn with_max_query_string_length_sets_value() {
207 let config = DispatchConfig::default().with_max_query_string_length(8192);
208 assert_eq!(config.max_query_string_length, 8192);
209 }
210
211 #[test]
212 fn with_sse_keep_alive_interval_sets_value() {
213 let config =
214 DispatchConfig::default().with_sse_keep_alive_interval(Duration::from_secs(15));
215 assert_eq!(config.sse_keep_alive_interval, Duration::from_secs(15));
216 }
217
218 #[test]
219 fn with_sse_channel_capacity_sets_value() {
220 let config = DispatchConfig::default().with_sse_channel_capacity(128);
221 assert_eq!(config.sse_channel_capacity, 128);
222 }
223
224 #[test]
225 fn with_max_batch_size_sets_value() {
226 let config = DispatchConfig::default().with_max_batch_size(50);
227 assert_eq!(config.max_batch_size, 50);
228 }
229
230 #[test]
231 fn builder_chaining() {
232 let config = DispatchConfig::default()
233 .with_max_request_body_size(1024)
234 .with_body_read_timeout(Duration::from_secs(10))
235 .with_max_query_string_length(2048)
236 .with_sse_keep_alive_interval(Duration::from_secs(5))
237 .with_sse_channel_capacity(32)
238 .with_max_batch_size(25);
239
240 assert_eq!(config.max_request_body_size, 1024);
241 assert_eq!(config.body_read_timeout, Duration::from_secs(10));
242 assert_eq!(config.max_query_string_length, 2048);
243 assert_eq!(config.sse_keep_alive_interval, Duration::from_secs(5));
244 assert_eq!(config.sse_channel_capacity, 32);
245 assert_eq!(config.max_batch_size, 25);
246 }
247
248 #[test]
249 fn debug_format() {
250 let config = DispatchConfig::default();
251 let debug = format!("{config:?}");
252 assert!(debug.contains("DispatchConfig"));
253 assert!(debug.contains("max_request_body_size"));
254 assert!(debug.contains("body_read_timeout"));
255 assert!(debug.contains("max_query_string_length"));
256 assert!(debug.contains("sse_keep_alive_interval"));
257 assert!(debug.contains("sse_channel_capacity"));
258 assert!(debug.contains("max_batch_size"));
259 }
260}