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::{Context, Result, helpers};
22
23#[derive(Debug, Serialize, Deserialize, Clone)]
24#[serde(rename_all = "kebab-case")]
25pub enum LogLevel {
27 Error,
29 Warn,
31 Info,
33 Debug,
35 Trace,
37}
38
39impl LogLevel {
40 pub fn name(&self) -> &'static str {
42 match self {
43 LogLevel::Error => "error",
44 LogLevel::Warn => "warn",
45 LogLevel::Info => "info",
46 LogLevel::Debug => "debug",
47 LogLevel::Trace => "trace",
48 }
49 }
50}
51
52#[cfg(any(
53 feature = "compression",
54 feature = "compression-gzip",
55 feature = "compression-brotli",
56 feature = "compression-zstd",
57 feature = "compression-deflate"
58))]
59#[cfg_attr(
60 docsrs,
61 doc(cfg(any(
62 feature = "compression",
63 feature = "compression-gzip",
64 feature = "compression-brotli",
65 feature = "compression-zstd",
66 feature = "compression-deflate"
67 )))
68)]
69#[derive(clap::ValueEnum, Debug, Serialize, Deserialize, Copy, Clone)]
70#[serde(rename_all = "kebab-case")]
71pub enum CompressionLevel {
73 Fastest,
75 Best,
77 Default,
79}
80
81#[cfg(any(
82 feature = "compression",
83 feature = "compression-gzip",
84 feature = "compression-brotli",
85 feature = "compression-zstd",
86 feature = "compression-deflate"
87))]
88#[cfg_attr(
89 docsrs,
90 doc(cfg(any(
91 feature = "compression",
92 feature = "compression-gzip",
93 feature = "compression-brotli",
94 feature = "compression-zstd",
95 feature = "compression-deflate"
96 )))
97)]
98impl CompressionLevel {
99 pub(crate) fn into_algorithm_level(self, default: i32) -> async_compression::Level {
102 match self {
103 Self::Fastest => async_compression::Level::Fastest,
104 Self::Best => async_compression::Level::Best,
105 Self::Default => async_compression::Level::Precise(default),
106 }
107 }
108}
109
110#[derive(Debug, Serialize, Deserialize, Clone)]
111#[serde(rename_all = "kebab-case")]
112pub struct Headers {
114 pub source: String,
116 #[serde(rename(deserialize = "headers"), with = "http_serde::header_map")]
117 pub headers: HeaderMap,
119}
120
121#[derive(Debug, Serialize_repr, Deserialize_repr, Clone)]
122#[repr(u16)]
123pub enum RedirectsKind {
125 Permanent = 301,
127 Temporary = 302,
129}
130
131#[derive(Debug, Serialize, Deserialize, Clone)]
132#[serde(rename_all = "kebab-case")]
133pub struct Redirects {
135 pub host: Option<String>,
137 pub source: String,
139 pub destination: String,
141 pub kind: RedirectsKind,
143}
144
145#[derive(Debug, Serialize, Deserialize, Clone)]
146#[serde(rename_all = "kebab-case")]
147pub struct Rewrites {
149 pub source: String,
151 pub destination: String,
153 pub redirect: Option<RedirectsKind>,
155}
156
157#[derive(Debug, Serialize, Deserialize, Clone)]
158#[serde(rename_all = "kebab-case")]
159pub struct VirtualHosts {
161 pub host: String,
163 pub root: Option<PathBuf>,
165}
166
167#[cfg(feature = "experimental")]
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 #[cfg(feature = "experimental")]
195 pub memory_cache: Option<MemoryCache>,
197}
198
199#[derive(Debug, Serialize, Deserialize, Clone)]
202#[serde(rename_all = "kebab-case")]
203pub struct General {
204 pub host: Option<String>,
206 pub port: Option<u16>,
208 pub root: Option<PathBuf>,
210
211 pub log_level: Option<LogLevel>,
213 pub log_with_ansi: Option<bool>,
215
216 pub cache_control_headers: Option<bool>,
218
219 #[cfg(any(
221 feature = "compression",
222 feature = "compression-gzip",
223 feature = "compression-brotli",
224 feature = "compression-zstd",
225 feature = "compression-deflate"
226 ))]
227 #[cfg_attr(
228 docsrs,
229 doc(cfg(any(
230 feature = "compression",
231 feature = "compression-gzip",
232 feature = "compression-brotli",
233 feature = "compression-zstd",
234 feature = "compression-deflate"
235 )))
236 )]
237 pub compression: Option<bool>,
238
239 #[cfg(any(
241 feature = "compression",
242 feature = "compression-gzip",
243 feature = "compression-brotli",
244 feature = "compression-zstd",
245 feature = "compression-deflate"
246 ))]
247 #[cfg_attr(
248 docsrs,
249 doc(cfg(any(
250 feature = "compression",
251 feature = "compression-gzip",
252 feature = "compression-brotli",
253 feature = "compression-zstd",
254 feature = "compression-deflate"
255 )))
256 )]
257 pub compression_level: Option<CompressionLevel>,
258
259 pub compression_static: Option<bool>,
261
262 pub page404: Option<PathBuf>,
264 pub page50x: Option<PathBuf>,
266
267 #[cfg(feature = "http2")]
269 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
270 pub http2: Option<bool>,
271 #[cfg(feature = "http2")]
273 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
274 pub http2_tls_cert: Option<PathBuf>,
275 #[cfg(feature = "http2")]
277 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
278 pub http2_tls_key: Option<PathBuf>,
279
280 #[cfg(feature = "http2")]
282 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
283 pub https_redirect: Option<bool>,
284 #[cfg(feature = "http2")]
286 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
287 pub https_redirect_host: Option<String>,
288 #[cfg(feature = "http2")]
290 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
291 pub https_redirect_from_port: Option<u16>,
292 #[cfg(feature = "http2")]
294 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
295 pub https_redirect_from_hosts: Option<String>,
296
297 pub security_headers: Option<bool>,
299
300 pub cors_allow_origins: Option<String>,
302 pub cors_allow_headers: Option<String>,
304 pub cors_expose_headers: Option<String>,
306
307 pub index_files: Option<String>,
309
310 #[cfg(feature = "directory-listing")]
312 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
313 pub directory_listing: Option<bool>,
314 #[cfg(feature = "directory-listing")]
316 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
317 pub directory_listing_order: Option<u8>,
318 #[cfg(feature = "directory-listing")]
320 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
321 pub directory_listing_format: Option<DirListFmt>,
322
323 #[cfg(feature = "directory-listing-download")]
325 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing-download")))]
326 pub directory_listing_download: Option<Vec<DirDownloadFmt>>,
327
328 #[cfg(feature = "basic-auth")]
330 #[cfg_attr(docsrs, doc(cfg(feature = "basic-auth")))]
331 pub basic_auth: Option<String>,
332
333 pub fd: Option<usize>,
335
336 pub threads_multiplier: Option<usize>,
338
339 pub max_blocking_threads: Option<usize>,
341
342 pub grace_period: Option<u8>,
344
345 #[cfg(feature = "fallback-page")]
347 #[cfg_attr(docsrs, doc(cfg(feature = "fallback-page")))]
348 pub page_fallback: Option<PathBuf>,
349
350 pub log_remote_address: Option<bool>,
352
353 pub log_x_real_ip: Option<bool>,
355
356 pub log_forwarded_for: Option<bool>,
358
359 pub trusted_proxies: Option<Vec<IpAddr>>,
361
362 pub redirect_trailing_slash: Option<bool>,
364
365 pub ignore_hidden_files: Option<bool>,
367
368 pub disable_symlinks: Option<bool>,
370
371 pub health: Option<bool>,
373
374 pub accept_markdown: Option<bool>,
376
377 #[cfg(all(unix, feature = "experimental"))]
378 pub experimental_metrics: Option<bool>,
380
381 pub maintenance_mode: Option<bool>,
383
384 pub maintenance_mode_status: Option<u16>,
386
387 pub maintenance_mode_file: Option<PathBuf>,
389
390 #[cfg(feature = "experimental")]
391 pub memory_cache: Option<bool>,
393
394 #[cfg(windows)]
395 pub windows_service: Option<bool>,
397}
398
399#[derive(Debug, Serialize, Deserialize, Clone)]
401#[serde(rename_all = "kebab-case")]
402pub struct Settings {
403 pub general: Option<General>,
405 pub advanced: Option<Advanced>,
407}
408
409impl Settings {
410 pub fn read(config_file: &Path) -> Result<Settings> {
412 let ext = config_file.extension();
414 if ext.is_none() || ext.unwrap().is_empty() || ext.unwrap().ne("toml") {
415 bail!("configuration file should be in toml format. E.g `sws.toml`");
416 }
417
418 let toml =
420 read_toml_file(config_file).with_context(|| "error reading toml configuration file")?;
421 let mut unused = BTreeSet::new();
422 let manifest: Settings = serde_ignored::deserialize(toml, |path| {
423 let mut key = String::new();
424 helpers::stringify(&mut key, &path);
425 unused.insert(key);
426 })
427 .with_context(|| "error during toml configuration file deserialization")?;
428
429 for key in unused {
430 println!("Warning: unused configuration manifest key \"{key}\" or unsupported");
431 }
432
433 Ok(manifest)
434 }
435}
436
437fn read_toml_file(path: &Path) -> Result<toml::Value> {
439 let toml_str = helpers::read_file(path).with_context(|| {
440 format!(
441 "error trying to deserialize toml configuration file at \"{}\"",
442 path.display()
443 )
444 })?;
445
446 toml::from_str(&toml_str)
447 .map_err(|e| anyhow::Error::from(e).context("could not parse input as TOML"))
448}