1use schemars::JsonSchema;
2use serde::Deserialize;
3use summer::config::Configurable;
4use std::net::{IpAddr, Ipv4Addr};
5use tracing::Level;
6
7summer::submit_config_schema!("web", WebConfig);
8
9#[cfg(feature = "socket_io")]
10summer::submit_config_schema!("socket_io", SocketIOConfig);
11
12#[derive(Debug, Configurable, JsonSchema, Deserialize)]
14#[config_prefix = "web"]
15pub struct WebConfig {
16 #[serde(flatten)]
17 pub(crate) server: ServerConfig,
18 #[cfg(feature = "openapi")]
19 pub(crate) openapi: OpenApiConfig,
20 pub(crate) middlewares: Option<Middlewares>,
21}
22
23#[derive(Debug, Clone, JsonSchema, Deserialize)]
24pub struct ServerConfig {
25 #[serde(default = "default_binding")]
26 pub(crate) binding: IpAddr,
27 #[serde(default = "default_port")]
28 pub(crate) port: u16,
29 #[serde(default)]
30 pub(crate) connect_info: bool,
31 #[serde(default = "default_true")]
32 pub(crate) graceful: bool,
33 #[serde(default)]
34 pub(crate) global_prefix: String,
35}
36
37#[cfg(feature = "openapi")]
38#[derive(Debug, Clone, JsonSchema, Deserialize)]
39pub struct OpenApiConfig {
40 #[serde(default = "default_doc_prefix")]
41 pub(crate) doc_prefix: String,
42 #[serde(default)]
43 pub(crate) info: aide::openapi::Info,
44}
45
46fn normalize_prefix(prefix: &mut String) {
49 if !prefix.is_empty() {
50 if !prefix.starts_with('/') {
51 prefix.insert(0, '/');
52 }
53 while prefix.ends_with('/') {
54 prefix.pop();
55 }
56 }
57}
58
59impl WebConfig {
60 pub(crate) fn normalize_prefixes(&mut self) {
61 normalize_prefix(&mut self.server.global_prefix);
62 #[cfg(feature = "openapi")]
63 normalize_prefix(&mut self.openapi.doc_prefix);
64 }
65}
66
67fn default_binding() -> IpAddr {
68 IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))
69}
70
71fn default_port() -> u16 {
72 8080
73}
74
75fn default_true() -> bool {
76 true
77}
78
79#[cfg(feature = "openapi")]
80fn default_doc_prefix() -> String {
81 "/docs".into()
82}
83
84#[derive(Debug, Clone, JsonSchema, Deserialize)]
86pub struct Middlewares {
87 pub compression: Option<EnableMiddleware>,
89 pub limit_payload: Option<LimitPayloadMiddleware>,
91 pub logger: Option<TraceLoggerMiddleware>,
94 pub catch_panic: Option<EnableMiddleware>,
96 pub timeout_request: Option<TimeoutRequestMiddleware>,
98 pub cors: Option<CorsMiddleware>,
100 #[serde(rename = "static")]
102 pub static_assets: Option<StaticAssetsMiddleware>,
103}
104
105#[derive(Debug, Clone, JsonSchema, Deserialize)]
107pub struct StaticAssetsMiddleware {
108 pub enable: bool,
110 #[serde(default = "bool::default")]
112 pub must_exist: bool,
113 #[serde(default = "default_fallback")]
116 pub fallback: String,
117 #[serde(default = "bool::default")]
119 pub precompressed: bool,
120 #[serde(default = "default_assets_uri")]
122 pub uri: String,
123 #[serde(default = "default_assets_path")]
125 pub path: String,
126}
127
128#[derive(Debug, Clone, JsonSchema, Deserialize)]
130pub struct TraceLoggerMiddleware {
131 pub enable: bool,
133 pub level: LogLevel,
134}
135
136#[derive(Debug, Default, Clone, JsonSchema, Deserialize)]
137pub enum LogLevel {
138 #[serde(rename = "trace")]
140 Trace,
141 #[serde(rename = "debug")]
143 Debug,
144 #[serde(rename = "info")]
146 #[default]
147 Info,
148 #[serde(rename = "warn")]
150 Warn,
151 #[serde(rename = "error")]
153 Error,
154}
155
156#[allow(clippy::from_over_into)]
157impl Into<Level> for LogLevel {
158 fn into(self) -> Level {
159 match self {
160 Self::Trace => Level::TRACE,
161 Self::Debug => Level::DEBUG,
162 Self::Info => Level::INFO,
163 Self::Warn => Level::WARN,
164 Self::Error => Level::ERROR,
165 }
166 }
167}
168
169#[derive(Debug, Clone, JsonSchema, Deserialize)]
171pub struct CorsMiddleware {
172 pub enable: bool,
174 pub allow_origins: Option<Vec<String>>,
176 pub allow_headers: Option<Vec<String>>,
178 pub allow_methods: Option<Vec<String>>,
180 pub max_age: Option<u64>,
182}
183
184#[derive(Debug, Clone, JsonSchema, Deserialize)]
186pub struct TimeoutRequestMiddleware {
187 pub enable: bool,
189 pub timeout: u64,
191}
192
193#[derive(Debug, Clone, JsonSchema, Deserialize)]
195pub struct LimitPayloadMiddleware {
196 pub enable: bool,
198 pub body_limit: String,
200}
201
202#[derive(Debug, PartialEq, Clone, JsonSchema, Deserialize)]
205pub struct EnableMiddleware {
206 pub enable: bool,
208}
209
210fn default_assets_path() -> String {
211 "static".to_string()
212}
213
214fn default_assets_uri() -> String {
215 "/static".to_string()
216}
217
218fn default_fallback() -> String {
219 "index.html".to_string()
220}
221
222#[cfg(feature = "socket_io")]
224#[derive(Debug, Configurable, JsonSchema, Deserialize)]
225#[config_prefix = "socket_io"]
226pub struct SocketIOConfig {
227 #[serde(default = "default_namespace")]
228 pub default_namespace: String,
229}
230
231#[cfg(feature = "socket_io")]
232fn default_namespace() -> String {
233 "/".to_string()
234}