1use headers::HeaderMap;
9use serde::Deserialize;
10use serde_repr::{Deserialize_repr, Serialize_repr};
11use std::net::IpAddr;
12use std::path::Path;
13use std::{collections::BTreeSet, path::PathBuf};
14
15#[cfg(feature = "directory-listing")]
16use crate::directory_listing::DirListFmt;
17
18#[cfg(feature = "directory-listing-download")]
19use crate::directory_listing::download::DirDownloadFmt;
20
21use crate::logger::LogFormat;
22use crate::{Context, Result, helpers};
23
24#[derive(Debug, Serialize, Deserialize, Clone)]
25#[serde(rename_all = "kebab-case")]
26pub enum LogLevel {
28 Error,
30 Warn,
32 Info,
34 Debug,
36 Trace,
38}
39
40impl LogLevel {
41 pub fn name(&self) -> &'static str {
43 match self {
44 LogLevel::Error => "error",
45 LogLevel::Warn => "warn",
46 LogLevel::Info => "info",
47 LogLevel::Debug => "debug",
48 LogLevel::Trace => "trace",
49 }
50 }
51}
52
53#[cfg(any(
54 feature = "compression",
55 feature = "compression-gzip",
56 feature = "compression-brotli",
57 feature = "compression-zstd",
58 feature = "compression-deflate"
59))]
60#[cfg_attr(
61 docsrs,
62 doc(cfg(any(
63 feature = "compression",
64 feature = "compression-gzip",
65 feature = "compression-brotli",
66 feature = "compression-zstd",
67 feature = "compression-deflate"
68 )))
69)]
70#[derive(clap::ValueEnum, Debug, Serialize, Deserialize, Copy, Clone)]
71#[serde(rename_all = "kebab-case")]
72pub enum CompressionLevel {
74 Fastest,
76 Best,
78 Default,
80}
81
82#[cfg(any(
83 feature = "compression",
84 feature = "compression-gzip",
85 feature = "compression-brotli",
86 feature = "compression-zstd",
87 feature = "compression-deflate"
88))]
89#[cfg_attr(
90 docsrs,
91 doc(cfg(any(
92 feature = "compression",
93 feature = "compression-gzip",
94 feature = "compression-brotli",
95 feature = "compression-zstd",
96 feature = "compression-deflate"
97 )))
98)]
99impl CompressionLevel {
100 pub(crate) fn into_algorithm_level(self, default: i32) -> async_compression::Level {
103 match self {
104 Self::Fastest => async_compression::Level::Fastest,
105 Self::Best => async_compression::Level::Best,
106 Self::Default => async_compression::Level::Precise(default),
107 }
108 }
109}
110
111#[derive(Debug, Serialize, Deserialize, Clone)]
112#[serde(rename_all = "kebab-case")]
113pub struct Headers {
115 pub source: String,
117 #[serde(rename(deserialize = "headers"), with = "http_serde::header_map")]
118 pub headers: HeaderMap,
120}
121
122#[derive(Debug, Serialize_repr, Deserialize_repr, Clone)]
123#[repr(u16)]
124pub enum RedirectsKind {
126 Permanent = 301,
128 Temporary = 302,
130}
131
132#[derive(Debug, Serialize, Deserialize, Clone)]
133#[serde(rename_all = "kebab-case")]
134pub struct Redirects {
136 pub host: Option<String>,
138 pub source: String,
140 pub destination: String,
142 pub kind: RedirectsKind,
144}
145
146#[derive(Debug, Serialize, Deserialize, Clone)]
147#[serde(rename_all = "kebab-case")]
148pub struct Rewrites {
150 pub source: String,
152 pub destination: String,
154 pub redirect: Option<RedirectsKind>,
156}
157
158#[derive(Debug, Serialize, Deserialize, Clone)]
159#[serde(rename_all = "kebab-case")]
160pub struct VirtualHosts {
162 pub host: String,
164 pub root: Option<PathBuf>,
166}
167
168#[derive(Debug, Serialize, Deserialize, Clone)]
169#[serde(rename_all = "kebab-case")]
170pub struct MemoryCache {
172 pub capacity: Option<u64>,
174 pub ttl: Option<u64>,
176 pub tti: Option<u64>,
178 pub max_file_size: Option<u64>,
180}
181
182#[derive(Debug, Serialize, Deserialize, Clone)]
184#[serde(rename_all = "kebab-case")]
185pub struct Advanced {
186 pub headers: Option<Vec<Headers>>,
188 pub rewrites: Option<Vec<Rewrites>>,
190 pub redirects: Option<Vec<Redirects>>,
192 pub virtual_hosts: Option<Vec<VirtualHosts>>,
194 pub memory_cache: Option<MemoryCache>,
196}
197
198#[derive(Debug, Serialize, Deserialize, Clone)]
201#[serde(rename_all = "kebab-case")]
202pub struct General {
203 pub host: Option<String>,
205 pub port: Option<u16>,
207 pub root: Option<PathBuf>,
209
210 pub log_level: Option<LogLevel>,
212 pub log_with_ansi: Option<bool>,
214 pub log_format: Option<LogFormat>,
216 pub log_file: Option<PathBuf>,
218
219 pub cache_control_headers: Option<bool>,
221
222 pub etag: Option<bool>,
224
225 #[cfg(any(
227 feature = "compression",
228 feature = "compression-gzip",
229 feature = "compression-brotli",
230 feature = "compression-zstd",
231 feature = "compression-deflate"
232 ))]
233 #[cfg_attr(
234 docsrs,
235 doc(cfg(any(
236 feature = "compression",
237 feature = "compression-gzip",
238 feature = "compression-brotli",
239 feature = "compression-zstd",
240 feature = "compression-deflate"
241 )))
242 )]
243 pub compression: Option<bool>,
244
245 #[cfg(any(
247 feature = "compression",
248 feature = "compression-gzip",
249 feature = "compression-brotli",
250 feature = "compression-zstd",
251 feature = "compression-deflate"
252 ))]
253 #[cfg_attr(
254 docsrs,
255 doc(cfg(any(
256 feature = "compression",
257 feature = "compression-gzip",
258 feature = "compression-brotli",
259 feature = "compression-zstd",
260 feature = "compression-deflate"
261 )))
262 )]
263 pub compression_level: Option<CompressionLevel>,
264
265 pub compression_static: Option<bool>,
267
268 pub page404: Option<PathBuf>,
270 pub page50x: Option<PathBuf>,
272
273 #[cfg(feature = "tls")]
275 #[cfg_attr(docsrs, doc(cfg(feature = "tls")))]
276 pub tls: Option<bool>,
277 #[cfg(feature = "tls")]
279 #[cfg_attr(docsrs, doc(cfg(feature = "tls")))]
280 pub tls_cert: Option<PathBuf>,
281 #[cfg(feature = "tls")]
283 #[cfg_attr(docsrs, doc(cfg(feature = "tls")))]
284 pub tls_key: Option<PathBuf>,
285
286 #[cfg(feature = "http2")]
288 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
289 pub http2: Option<bool>,
290
291 #[cfg(feature = "tls")]
293 #[cfg_attr(docsrs, doc(cfg(feature = "tls")))]
294 pub https_redirect: Option<bool>,
295 #[cfg(feature = "tls")]
297 #[cfg_attr(docsrs, doc(cfg(feature = "tls")))]
298 pub https_redirect_host: Option<String>,
299 #[cfg(feature = "tls")]
301 #[cfg_attr(docsrs, doc(cfg(feature = "tls")))]
302 pub https_redirect_from_port: Option<u16>,
303 #[cfg(feature = "tls")]
305 #[cfg_attr(docsrs, doc(cfg(feature = "tls")))]
306 pub https_redirect_from_hosts: Option<String>,
307
308 pub security_headers: Option<bool>,
310
311 pub cors_allow_origins: Option<String>,
313 pub cors_allow_headers: Option<String>,
315 pub cors_expose_headers: Option<String>,
317
318 pub index_files: Option<String>,
320
321 #[cfg(feature = "directory-listing")]
323 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
324 pub directory_listing: Option<bool>,
325 #[cfg(feature = "directory-listing")]
327 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
328 pub directory_listing_order: Option<u8>,
329 #[cfg(feature = "directory-listing")]
331 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
332 pub directory_listing_format: Option<DirListFmt>,
333
334 #[cfg(feature = "directory-listing-download")]
336 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing-download")))]
337 pub directory_listing_download: Option<Vec<DirDownloadFmt>>,
338
339 #[cfg(feature = "basic-auth")]
341 #[cfg_attr(docsrs, doc(cfg(feature = "basic-auth")))]
342 pub basic_auth: Option<String>,
343
344 pub fd: Option<usize>,
346
347 #[cfg(unix)]
349 pub unix_socket: Option<PathBuf>,
350
351 #[cfg(unix)]
353 pub unix_socket_mode: Option<u32>,
354
355 #[cfg(unix)]
357 pub unix_socket_force: Option<bool>,
358
359 pub threads_multiplier: Option<usize>,
361
362 pub max_blocking_threads: Option<usize>,
364
365 pub grace_period: Option<u8>,
367
368 #[cfg(feature = "fallback-page")]
370 #[cfg_attr(docsrs, doc(cfg(feature = "fallback-page")))]
371 pub page_fallback: Option<PathBuf>,
372
373 pub log_remote_address: Option<bool>,
375
376 pub log_x_real_ip: Option<bool>,
378
379 pub log_forwarded_for: Option<bool>,
381
382 pub trusted_proxies: Option<Vec<IpAddr>>,
384
385 pub redirect_trailing_slash: Option<bool>,
387
388 pub include_hidden: Option<bool>,
390
391 pub follow_symlinks: Option<bool>,
393
394 pub use_relative_root: Option<bool>,
396
397 pub health: Option<bool>,
399
400 pub accept_markdown: Option<bool>,
402
403 pub text_charset: Option<bool>,
405
406 #[cfg(feature = "metrics")]
407 pub metrics: Option<bool>,
409
410 pub maintenance_mode: Option<bool>,
412
413 pub maintenance_mode_status: Option<u16>,
415
416 pub maintenance_mode_file: Option<PathBuf>,
418
419 #[cfg(windows)]
420 pub windows_service: Option<bool>,
422}
423
424#[derive(Debug, Serialize, Deserialize, Clone)]
426#[serde(rename_all = "kebab-case")]
427pub struct Settings {
428 pub general: Option<General>,
430 pub advanced: Option<Advanced>,
432}
433
434impl Settings {
435 pub fn read(config_file: &Path) -> Result<Settings> {
437 if !matches!(config_file.extension(), Some(ext) if !ext.is_empty() && ext == "toml") {
439 bail!("configuration file should be in toml format. E.g `sws.toml`");
440 }
441
442 let toml =
444 read_toml_file(config_file).with_context(|| "error reading toml configuration file")?;
445 let mut unused = BTreeSet::new();
446 let manifest: Settings = serde_ignored::deserialize(toml, |path| {
447 let mut key = String::new();
448 helpers::stringify(&mut key, &path);
449 unused.insert(key);
450 })
451 .with_context(|| "error during toml configuration file deserialization")?;
452
453 for key in unused {
454 println!("Warning: unused configuration manifest key \"{key}\" or unsupported");
455 }
456
457 Ok(manifest)
458 }
459}
460
461fn read_toml_file(path: &Path) -> Result<toml::Value> {
463 let toml_str = helpers::read_file(path).with_context(|| {
464 format!(
465 "error trying to deserialize toml configuration file at \"{}\"",
466 path.display()
467 )
468 })?;
469
470 toml::from_str(&toml_str)
471 .map_err(|e| anyhow::Error::from(e).context("could not parse input as TOML"))
472}