Skip to main content

summer_web/
config.rs

1use schemars::JsonSchema;
2use serde::Deserialize;
3use std::net::{IpAddr, Ipv4Addr};
4use summer::config::Configurable;
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/// summer-web Config
13#[derive(Debug, Configurable, JsonSchema, Deserialize)]
14#[config_prefix = "web"]
15pub struct WebConfig {
16    #[serde(flatten)]
17    pub(crate) server: ServerConfig,
18    /// Omitted in TOML when all fields use their defaults (common in tests where
19    /// `summer-web/openapi` is pulled in transitively, e.g. via `summer-macros`).
20    #[cfg(feature = "openapi")]
21    #[serde(default)]
22    pub(crate) openapi: OpenApiConfig,
23    pub(crate) middlewares: Option<Middlewares>,
24}
25
26#[derive(Debug, Clone, JsonSchema, Deserialize)]
27pub struct ServerConfig {
28    #[serde(default = "default_binding")]
29    pub binding: IpAddr,
30    #[serde(default = "default_port")]
31    pub port: u16,
32    #[serde(default)]
33    pub connect_info: bool,
34    #[serde(default = "default_true")]
35    pub graceful: bool,
36    #[serde(default)]
37    pub global_prefix: String,
38}
39
40#[cfg(feature = "openapi")]
41#[derive(Debug, Clone, JsonSchema, Deserialize)]
42pub struct OpenApiConfig {
43    #[serde(default = "default_doc_prefix")]
44    pub(crate) doc_prefix: String,
45    #[serde(default)]
46    pub(crate) info: aide::openapi::Info,
47}
48
49#[cfg(feature = "openapi")]
50impl Default for OpenApiConfig {
51    fn default() -> Self {
52        Self {
53            doc_prefix: default_doc_prefix(),
54            info: aide::openapi::Info::default(),
55        }
56    }
57}
58
59/// Normalize a URL prefix: ensure it starts with '/' and does not end with '/'.
60/// Empty strings are left unchanged.
61fn normalize_prefix(prefix: &mut String) {
62    if !prefix.is_empty() {
63        if !prefix.starts_with('/') {
64            prefix.insert(0, '/');
65        }
66        while prefix.ends_with('/') {
67            prefix.pop();
68        }
69    }
70}
71
72impl WebConfig {
73    pub(crate) fn normalize_prefixes(&mut self) {
74        normalize_prefix(&mut self.server.global_prefix);
75        #[cfg(feature = "openapi")]
76        normalize_prefix(&mut self.openapi.doc_prefix);
77    }
78}
79
80fn default_binding() -> IpAddr {
81    IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))
82}
83
84fn default_port() -> u16 {
85    8080
86}
87
88fn default_true() -> bool {
89    true
90}
91
92#[cfg(feature = "openapi")]
93fn default_doc_prefix() -> String {
94    "/docs".into()
95}
96
97/// Server middleware configuration structure.
98#[derive(Debug, Clone, JsonSchema, Deserialize)]
99pub struct Middlewares {
100    /// Middleware that enable compression for the response.
101    pub compression: Option<EnableMiddleware>,
102    /// Middleware that limit the payload request.
103    pub limit_payload: Option<LimitPayloadMiddleware>,
104    /// Middleware that improve the tracing logger and adding trace id for each
105    /// request.
106    pub logger: Option<TraceLoggerMiddleware>,
107    /// catch any code panic and log the error.
108    pub catch_panic: Option<EnableMiddleware>,
109    /// Setting a global timeout for the requests
110    pub timeout_request: Option<TimeoutRequestMiddleware>,
111    /// Setting cors configuration
112    pub cors: Option<CorsMiddleware>,
113    /// Serving static assets
114    #[serde(rename = "static")]
115    pub static_assets: Option<StaticAssetsMiddleware>,
116}
117
118/// Static asset middleware configuration
119#[derive(Debug, Clone, JsonSchema, Deserialize)]
120pub struct StaticAssetsMiddleware {
121    /// toggle enable
122    pub enable: bool,
123    /// Check that assets must exist on disk
124    #[serde(default = "bool::default")]
125    pub must_exist: bool,
126    /// Fallback page for a case when no asset exists (404). Useful for SPA
127    /// (single page app) where routes are virtual.
128    #[serde(default = "default_fallback")]
129    pub fallback: String,
130    /// Enable `precompressed_gzip`
131    #[serde(default = "bool::default")]
132    pub precompressed: bool,
133    /// Uri for the assets
134    #[serde(default = "default_assets_uri")]
135    pub uri: String,
136    /// Path for the assets
137    #[serde(default = "default_assets_path")]
138    pub path: String,
139}
140
141/// CORS middleware configuration
142#[derive(Debug, Clone, JsonSchema, Deserialize)]
143pub struct TraceLoggerMiddleware {
144    /// toggle enable
145    pub enable: bool,
146    pub level: LogLevel,
147}
148
149#[derive(Debug, Default, Clone, JsonSchema, Deserialize)]
150pub enum LogLevel {
151    /// The "trace" level.
152    #[serde(rename = "trace")]
153    Trace,
154    /// The "debug" level.
155    #[serde(rename = "debug")]
156    Debug,
157    /// The "info" level.
158    #[serde(rename = "info")]
159    #[default]
160    Info,
161    /// The "warn" level.
162    #[serde(rename = "warn")]
163    Warn,
164    /// The "error" level.
165    #[serde(rename = "error")]
166    Error,
167}
168
169#[allow(clippy::from_over_into)]
170impl Into<Level> for LogLevel {
171    fn into(self) -> Level {
172        match self {
173            Self::Trace => Level::TRACE,
174            Self::Debug => Level::DEBUG,
175            Self::Info => Level::INFO,
176            Self::Warn => Level::WARN,
177            Self::Error => Level::ERROR,
178        }
179    }
180}
181
182/// CORS middleware configuration
183#[derive(Debug, Clone, JsonSchema, Deserialize)]
184pub struct CorsMiddleware {
185    /// toggle enable
186    pub enable: bool,
187    /// Allow origins
188    pub allow_origins: Option<Vec<String>>,
189    /// Allow headers
190    pub allow_headers: Option<Vec<String>>,
191    /// Allow methods
192    pub allow_methods: Option<Vec<String>>,
193    /// Max age
194    pub max_age: Option<u64>,
195}
196
197/// Timeout middleware configuration
198#[derive(Debug, Clone, JsonSchema, Deserialize)]
199pub struct TimeoutRequestMiddleware {
200    /// toggle enable
201    pub enable: bool,
202    /// Timeout request in milliseconds
203    pub timeout: u64,
204}
205
206/// Limit payload size middleware configuration
207#[derive(Debug, Clone, JsonSchema, Deserialize)]
208pub struct LimitPayloadMiddleware {
209    /// toggle enable
210    pub enable: bool,
211    /// Body limit. for example: 5mb
212    pub body_limit: String,
213}
214
215/// A generic middleware configuration that can be enabled or
216/// disabled.
217#[derive(Debug, PartialEq, Clone, JsonSchema, Deserialize)]
218pub struct EnableMiddleware {
219    /// toggle enable
220    pub enable: bool,
221}
222
223fn default_assets_path() -> String {
224    "static".to_string()
225}
226
227fn default_assets_uri() -> String {
228    "/static".to_string()
229}
230
231fn default_fallback() -> String {
232    "index.html".to_string()
233}
234
235/// SocketIO configuration
236#[cfg(feature = "socket_io")]
237#[derive(Debug, Configurable, JsonSchema, Deserialize)]
238#[config_prefix = "socket_io"]
239pub struct SocketIOConfig {
240    #[serde(default = "default_namespace")]
241    pub default_namespace: String,
242}
243
244#[cfg(feature = "socket_io")]
245fn default_namespace() -> String {
246    "/".to_string()
247}