siderite_core/middleware/
cors.rs1use super::adapt::{BoxService, impl_layer, wrap_engine};
5use http::{HeaderName, HeaderValue, Method};
6use std::time::Duration;
7use tower_http::compression::CompressionLayer;
8use tower_http::cors::{AllowHeaders, AllowMethods, AllowOrigin, CorsLayer};
9
10#[derive(Debug, Clone, Default)]
11enum Allow<T> {
12 #[default]
13 Nothing,
14 Any,
15 List(Vec<T>),
16}
17
18#[derive(Debug, Clone, Default)]
25pub struct Cors {
26 origins: Allow<HeaderValue>,
27 methods: Allow<Method>,
28 headers: Allow<HeaderName>,
29 expose: Vec<HeaderName>,
30 credentials: bool,
31 max_age: Option<Duration>,
32}
33
34impl Cors {
35 pub fn new() -> Self {
37 Self::default()
38 }
39
40 pub fn permissive() -> Self {
42 Self::new()
43 .allow_any_origin()
44 .allow_any_method()
45 .allow_any_header()
46 }
47
48 #[must_use]
51 pub fn allow_origin(mut self, origin: &str) -> Self {
52 match HeaderValue::from_str(origin) {
53 Ok(value) => push(&mut self.origins, value),
54 Err(_) => tracing::warn!(origin, "ignoring invalid CORS origin"),
55 }
56 self
57 }
58
59 #[must_use]
61 pub fn allow_any_origin(mut self) -> Self {
62 self.origins = Allow::Any;
63 self
64 }
65
66 #[must_use]
68 pub fn allow_method(mut self, method: Method) -> Self {
69 push(&mut self.methods, method);
70 self
71 }
72
73 #[must_use]
75 pub fn allow_any_method(mut self) -> Self {
76 self.methods = Allow::Any;
77 self
78 }
79
80 #[must_use]
82 pub fn allow_header(mut self, name: &str) -> Self {
83 match HeaderName::from_bytes(name.as_bytes()) {
84 Ok(name) => push(&mut self.headers, name),
85 Err(_) => tracing::warn!(name, "ignoring invalid CORS header name"),
86 }
87 self
88 }
89
90 #[must_use]
92 pub fn allow_any_header(mut self) -> Self {
93 self.headers = Allow::Any;
94 self
95 }
96
97 #[must_use]
99 pub fn expose_header(mut self, name: &str) -> Self {
100 match HeaderName::from_bytes(name.as_bytes()) {
101 Ok(name) => self.expose.push(name),
102 Err(_) => tracing::warn!(name, "ignoring invalid CORS header name"),
103 }
104 self
105 }
106
107 #[must_use]
109 pub fn allow_credentials(mut self, allow: bool) -> Self {
110 self.credentials = allow;
111 self
112 }
113
114 #[must_use]
116 pub fn max_age(mut self, max_age: Duration) -> Self {
117 self.max_age = Some(max_age);
118 self
119 }
120
121 fn wrap(&self, inner: BoxService) -> BoxService {
122 let mirror = self.credentials;
123 let origin = match &self.origins {
124 Allow::Nothing => AllowOrigin::list([]),
125 Allow::Any if mirror => AllowOrigin::mirror_request(),
126 Allow::Any => AllowOrigin::any(),
127 Allow::List(v) => AllowOrigin::list(v.clone()),
128 };
129 let methods = match &self.methods {
130 Allow::Nothing => AllowMethods::list([]),
131 Allow::Any if mirror => AllowMethods::mirror_request(),
132 Allow::Any => AllowMethods::any(),
133 Allow::List(v) => AllowMethods::list(v.clone()),
134 };
135 let headers = match &self.headers {
136 Allow::Nothing => AllowHeaders::list([]),
137 Allow::Any if mirror => AllowHeaders::mirror_request(),
138 Allow::Any => AllowHeaders::any(),
139 Allow::List(v) => AllowHeaders::list(v.clone()),
140 };
141 let mut layer = CorsLayer::new()
142 .allow_origin(origin)
143 .allow_methods(methods)
144 .allow_headers(headers)
145 .expose_headers(self.expose.clone())
146 .allow_credentials(self.credentials);
147 if let Some(age) = self.max_age {
148 layer = layer.max_age(age);
149 }
150 wrap_engine(&layer, inner)
151 }
152}
153
154fn push<T>(slot: &mut Allow<T>, item: T) {
155 match slot {
156 Allow::List(items) => items.push(item),
157 other => *other = Allow::List(vec![item]),
158 }
159}
160
161#[derive(Debug, Clone)]
165pub struct Compression {
166 gzip: bool,
167 brotli: bool,
168}
169
170impl Default for Compression {
171 fn default() -> Self {
172 Self {
173 gzip: true,
174 brotli: true,
175 }
176 }
177}
178
179impl Compression {
180 pub fn new() -> Self {
182 Self::default()
183 }
184
185 #[must_use]
187 pub fn gzip(mut self, enabled: bool) -> Self {
188 self.gzip = enabled;
189 self
190 }
191
192 #[must_use]
194 pub fn brotli(mut self, enabled: bool) -> Self {
195 self.brotli = enabled;
196 self
197 }
198
199 fn wrap(&self, inner: BoxService) -> BoxService {
200 let layer = CompressionLayer::new().gzip(self.gzip).br(self.brotli);
201 wrap_engine(&layer, inner)
202 }
203}
204
205impl_layer!(Cors, Compression);