systemprompt_api/services/middleware/
cors.rs1use axum::http::Method;
7use systemprompt_models::Config;
8use thiserror::Error;
9use tower_http::cors::{AllowOrigin, CorsLayer};
10
11#[derive(Debug, Error)]
12pub enum CorsError {
13 #[error("Invalid origin '{origin}' in cors_allowed_origins: {reason}")]
14 InvalidOrigin { origin: String, reason: String },
15 #[error("cors_allowed_origins must contain at least one valid origin")]
16 EmptyOrigins,
17}
18
19#[derive(Debug, Clone, Copy)]
20pub struct CorsMiddleware;
21
22impl CorsMiddleware {
23 pub fn build_layer(config: &Config) -> Result<CorsLayer, CorsError> {
24 let mut origins = Vec::new();
25 for origin in &config.cors_allowed_origins {
26 let trimmed = origin.trim();
27 if trimmed.is_empty() {
28 continue;
29 }
30 let header_value =
31 trimmed
32 .parse::<http::HeaderValue>()
33 .map_err(|e| CorsError::InvalidOrigin {
34 origin: origin.clone(),
35 reason: e.to_string(),
36 })?;
37 origins.push(header_value);
38 }
39
40 if origins.is_empty() {
41 return Err(CorsError::EmptyOrigins);
42 }
43
44 Ok(CorsLayer::new()
45 .allow_origin(AllowOrigin::list(origins))
46 .allow_credentials(true)
47 .allow_methods([
48 Method::GET,
49 Method::POST,
50 Method::PUT,
51 Method::DELETE,
52 Method::OPTIONS,
53 ])
54 .allow_headers([
55 http::header::AUTHORIZATION,
56 http::header::CONTENT_TYPE,
57 http::header::ACCEPT,
58 http::header::ORIGIN,
59 http::header::ACCESS_CONTROL_REQUEST_METHOD,
60 http::header::ACCESS_CONTROL_REQUEST_HEADERS,
61 http::HeaderName::from_static("mcp-protocol-version"),
62 http::HeaderName::from_static("x-context-id"),
63 http::HeaderName::from_static("x-gateway-conversation-id"),
64 http::HeaderName::from_static("x-provider-request-id"),
65 http::HeaderName::from_static("x-trace-id"),
66 http::HeaderName::from_static("x-call-source"),
67 ])
68 .expose_headers([http::header::WWW_AUTHENTICATE]))
69 }
70}