1use rust_webx_core::error::Result;
7use rust_webx_core::http::IHttpContext;
8use rust_webx_core::middleware::IMiddleware;
9use std::ops::ControlFlow;
10
11#[derive(Debug, Clone)]
13pub struct CorsConfig {
14 pub origins: Vec<String>,
16 pub methods: Vec<String>,
18 pub headers: Vec<String>,
20 pub allow_credentials: bool,
22 pub max_age: u32,
24}
25
26impl Default for CorsConfig {
27 fn default() -> Self {
28 Self {
29 origins: vec!["*".to_string()],
30 methods: vec![
31 "GET".to_string(),
32 "POST".to_string(),
33 "PUT".to_string(),
34 "DELETE".to_string(),
35 "PATCH".to_string(),
36 "OPTIONS".to_string(),
37 ],
38 headers: vec!["Content-Type".to_string(), "Authorization".to_string()],
39 allow_credentials: false,
40 max_age: 86400,
41 }
42 }
43}
44
45pub struct CorsMiddleware {
50 config: CorsConfig,
51}
52
53impl CorsMiddleware {
54 pub fn new(config: CorsConfig) -> Self {
56 Self { config }
57 }
58}
59
60#[async_trait::async_trait]
61impl IMiddleware for CorsMiddleware {
62 async fn invoke(&self, ctx: &mut dyn IHttpContext) -> Result<ControlFlow<()>> {
63 let origin = ctx.request().header("origin");
64
65 let allowed = self
67 .config
68 .origins
69 .iter()
70 .find(|o| *o == "*" || origin.is_some_and(|orig| *o == orig))
71 .cloned();
72
73 if let Some(ref origin_value) = allowed {
74 let header_value = if origin_value == "*" {
75 "*".to_string()
76 } else if let Some(o) = origin {
77 o.to_string()
78 } else {
79 origin_value.clone()
80 };
81
82 ctx.response_mut()
83 .set_header("access-control-allow-origin", &header_value);
84
85 if self.config.allow_credentials {
86 ctx.response_mut()
87 .set_header("access-control-allow-credentials", "true");
88 }
89 }
90
91 if ctx.request().method().to_uppercase() == "OPTIONS" {
93 ctx.response_mut().set_status(204);
94 ctx.response_mut().set_header(
95 "access-control-allow-methods",
96 &self.config.methods.join(", "),
97 );
98 ctx.response_mut().set_header(
99 "access-control-allow-headers",
100 &self.config.headers.join(", "),
101 );
102 ctx.response_mut()
103 .set_header("access-control-max-age", &self.config.max_age.to_string());
104 return Ok(ControlFlow::Break(()));
107 }
108
109 if let Some(ref expose) = allowed {
111 if expose != "*" {
112 ctx.response_mut().set_header(
113 "access-control-expose-headers",
114 &self.config.headers.join(", "),
115 );
116 }
117 }
118
119 Ok(ControlFlow::Continue(()))
120 }
121}