Skip to main content

a2a_protocol_server/dispatch/
cors.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Tom F.
3
4//! CORS (Cross-Origin Resource Sharing) configuration for A2A dispatchers.
5//!
6//! Browser-based A2A clients need CORS headers to interact with agents.
7//! [`CorsConfig`] provides configurable CORS support that can be applied to
8//! both [`RestDispatcher`](super::RestDispatcher) and
9//! [`JsonRpcDispatcher`](super::JsonRpcDispatcher).
10
11use std::convert::Infallible;
12
13use bytes::Bytes;
14use http_body_util::combinators::BoxBody;
15use http_body_util::{BodyExt, Full};
16
17/// CORS configuration for A2A dispatchers.
18///
19/// # Examples
20///
21/// ```
22/// use a2a_protocol_server::dispatch::cors::CorsConfig;
23///
24/// // Allow all origins (development/testing).
25/// let cors = CorsConfig::permissive();
26///
27/// // Restrict to a specific origin.
28/// let cors = CorsConfig::new("https://my-app.example.com");
29/// ```
30#[derive(Debug, Clone)]
31pub struct CorsConfig {
32    /// The `Access-Control-Allow-Origin` value.
33    pub allow_origin: String,
34    /// The `Access-Control-Allow-Methods` value.
35    pub allow_methods: String,
36    /// The `Access-Control-Allow-Headers` value.
37    pub allow_headers: String,
38    /// The `Access-Control-Max-Age` value in seconds.
39    pub max_age_secs: u32,
40}
41
42impl CorsConfig {
43    /// Creates a new [`CorsConfig`] with the given allowed origin.
44    #[must_use]
45    pub fn new(allow_origin: impl Into<String>) -> Self {
46        Self {
47            allow_origin: allow_origin.into(),
48            allow_methods: "GET, POST, PUT, DELETE, OPTIONS".into(),
49            allow_headers: "content-type, authorization, a2a-notification-token".into(),
50            max_age_secs: 86400,
51        }
52    }
53
54    /// Creates a permissive [`CorsConfig`] that allows all origins.
55    ///
56    /// Suitable for development or public APIs. For production use,
57    /// prefer [`CorsConfig::new`] with a specific origin.
58    #[must_use]
59    pub fn permissive() -> Self {
60        Self::new("*")
61    }
62
63    /// Applies CORS headers to an existing HTTP response.
64    pub fn apply_headers<B>(&self, resp: &mut hyper::Response<B>) {
65        let headers = resp.headers_mut();
66        // These `parse()` calls only fail on invalid header values containing
67        // control characters, which our constructors don't produce.
68        if let Ok(v) = self.allow_origin.parse() {
69            headers.insert("access-control-allow-origin", v);
70        }
71        if let Ok(v) = self.allow_methods.parse() {
72            headers.insert("access-control-allow-methods", v);
73        }
74        if let Ok(v) = self.allow_headers.parse() {
75            headers.insert("access-control-allow-headers", v);
76        }
77        if let Ok(v) = self.max_age_secs.to_string().parse() {
78            headers.insert("access-control-max-age", v);
79        }
80    }
81
82    /// Builds a preflight (OPTIONS) response with CORS headers.
83    #[must_use]
84    pub fn preflight_response(&self) -> hyper::Response<BoxBody<Bytes, Infallible>> {
85        let mut resp = hyper::Response::builder()
86            .status(204)
87            .body(Full::new(Bytes::new()).boxed())
88            .unwrap_or_else(|_| hyper::Response::new(Full::new(Bytes::new()).boxed()));
89        self.apply_headers(&mut resp);
90        resp
91    }
92}