Skip to main content

summer_web/
config.rs

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/// 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    #[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
46/// Normalize a URL prefix: ensure it starts with '/' and does not end with '/'.
47/// Empty strings are left unchanged.
48fn 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/// Server middleware configuration structure.
85#[derive(Debug, Clone, JsonSchema, Deserialize)]
86pub struct Middlewares {
87    /// Middleware that enable compression for the response.
88    pub compression: Option<EnableMiddleware>,
89    /// Middleware that limit the payload request.
90    pub limit_payload: Option<LimitPayloadMiddleware>,
91    /// Middleware that improve the tracing logger and adding trace id for each
92    /// request.
93    pub logger: Option<TraceLoggerMiddleware>,
94    /// catch any code panic and log the error.
95    pub catch_panic: Option<EnableMiddleware>,
96    /// Setting a global timeout for the requests
97    pub timeout_request: Option<TimeoutRequestMiddleware>,
98    /// Setting cors configuration
99    pub cors: Option<CorsMiddleware>,
100    /// Serving static assets
101    #[serde(rename = "static")]
102    pub static_assets: Option<StaticAssetsMiddleware>,
103}
104
105/// Static asset middleware configuration
106#[derive(Debug, Clone, JsonSchema, Deserialize)]
107pub struct StaticAssetsMiddleware {
108    /// toggle enable
109    pub enable: bool,
110    /// Check that assets must exist on disk
111    #[serde(default = "bool::default")]
112    pub must_exist: bool,
113    /// Fallback page for a case when no asset exists (404). Useful for SPA
114    /// (single page app) where routes are virtual.
115    #[serde(default = "default_fallback")]
116    pub fallback: String,
117    /// Enable `precompressed_gzip`
118    #[serde(default = "bool::default")]
119    pub precompressed: bool,
120    /// Uri for the assets
121    #[serde(default = "default_assets_uri")]
122    pub uri: String,
123    /// Path for the assets
124    #[serde(default = "default_assets_path")]
125    pub path: String,
126}
127
128/// CORS middleware configuration
129#[derive(Debug, Clone, JsonSchema, Deserialize)]
130pub struct TraceLoggerMiddleware {
131    /// toggle enable
132    pub enable: bool,
133    pub level: LogLevel,
134}
135
136#[derive(Debug, Default, Clone, JsonSchema, Deserialize)]
137pub enum LogLevel {
138    /// The "trace" level.
139    #[serde(rename = "trace")]
140    Trace,
141    /// The "debug" level.
142    #[serde(rename = "debug")]
143    Debug,
144    /// The "info" level.
145    #[serde(rename = "info")]
146    #[default]
147    Info,
148    /// The "warn" level.
149    #[serde(rename = "warn")]
150    Warn,
151    /// The "error" level.
152    #[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/// CORS middleware configuration
170#[derive(Debug, Clone, JsonSchema, Deserialize)]
171pub struct CorsMiddleware {
172    /// toggle enable
173    pub enable: bool,
174    /// Allow origins
175    pub allow_origins: Option<Vec<String>>,
176    /// Allow headers
177    pub allow_headers: Option<Vec<String>>,
178    /// Allow methods
179    pub allow_methods: Option<Vec<String>>,
180    /// Max age
181    pub max_age: Option<u64>,
182}
183
184/// Timeout middleware configuration
185#[derive(Debug, Clone, JsonSchema, Deserialize)]
186pub struct TimeoutRequestMiddleware {
187    /// toggle enable
188    pub enable: bool,
189    /// Timeout request in milliseconds
190    pub timeout: u64,
191}
192
193/// Limit payload size middleware configuration
194#[derive(Debug, Clone, JsonSchema, Deserialize)]
195pub struct LimitPayloadMiddleware {
196    /// toggle enable
197    pub enable: bool,
198    /// Body limit. for example: 5mb
199    pub body_limit: String,
200}
201
202/// A generic middleware configuration that can be enabled or
203/// disabled.
204#[derive(Debug, PartialEq, Clone, JsonSchema, Deserialize)]
205pub struct EnableMiddleware {
206    /// toggle enable
207    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/// SocketIO configuration
223#[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}