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::{helpers, Context, Result};
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 #[cfg(any(
261 feature = "compression",
262 feature = "compression-gzip",
263 feature = "compression-brotli",
264 feature = "compression-zstd",
265 feature = "compression-deflate"
266 ))]
267 #[cfg_attr(
268 docsrs,
269 doc(cfg(any(
270 feature = "compression",
271 feature = "compression-gzip",
272 feature = "compression-brotli",
273 feature = "compression-zstd",
274 feature = "compression-deflate"
275 )))
276 )]
277 pub compression_static: Option<bool>,
278
279 pub page404: Option<PathBuf>,
281 pub page50x: Option<PathBuf>,
283
284 #[cfg(feature = "http2")]
286 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
287 pub http2: Option<bool>,
288 #[cfg(feature = "http2")]
290 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
291 pub http2_tls_cert: Option<PathBuf>,
292 #[cfg(feature = "http2")]
294 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
295 pub http2_tls_key: Option<PathBuf>,
296
297 #[cfg(feature = "http2")]
299 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
300 pub https_redirect: Option<bool>,
301 #[cfg(feature = "http2")]
303 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
304 pub https_redirect_host: Option<String>,
305 #[cfg(feature = "http2")]
307 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
308 pub https_redirect_from_port: Option<u16>,
309 #[cfg(feature = "http2")]
311 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
312 pub https_redirect_from_hosts: Option<String>,
313
314 pub security_headers: Option<bool>,
316
317 pub cors_allow_origins: Option<String>,
319 pub cors_allow_headers: Option<String>,
321 pub cors_expose_headers: Option<String>,
323
324 pub index_files: Option<String>,
326
327 #[cfg(feature = "directory-listing")]
329 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
330 pub directory_listing: Option<bool>,
331 #[cfg(feature = "directory-listing")]
333 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
334 pub directory_listing_order: Option<u8>,
335 #[cfg(feature = "directory-listing")]
337 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
338 pub directory_listing_format: Option<DirListFmt>,
339
340 #[cfg(feature = "directory-listing-download")]
342 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing-download")))]
343 pub directory_listing_download: Option<Vec<DirDownloadFmt>>,
344
345 #[cfg(feature = "basic-auth")]
347 #[cfg_attr(docsrs, doc(cfg(feature = "basic-auth")))]
348 pub basic_auth: Option<String>,
349
350 pub fd: Option<usize>,
352
353 pub threads_multiplier: Option<usize>,
355
356 pub max_blocking_threads: Option<usize>,
358
359 pub grace_period: Option<u8>,
361
362 #[cfg(feature = "fallback-page")]
364 #[cfg_attr(docsrs, doc(cfg(feature = "fallback-page")))]
365 pub page_fallback: Option<PathBuf>,
366
367 pub log_remote_address: Option<bool>,
369
370 pub log_x_real_ip: Option<bool>,
372
373 pub log_forwarded_for: Option<bool>,
375
376 pub trusted_proxies: Option<Vec<IpAddr>>,
378
379 pub redirect_trailing_slash: Option<bool>,
381
382 pub ignore_hidden_files: Option<bool>,
384
385 pub disable_symlinks: Option<bool>,
387
388 pub health: Option<bool>,
390
391 #[cfg(all(unix, feature = "experimental"))]
392 pub experimental_metrics: Option<bool>,
394
395 pub maintenance_mode: Option<bool>,
397
398 pub maintenance_mode_status: Option<u16>,
400
401 pub maintenance_mode_file: Option<PathBuf>,
403
404 #[cfg(feature = "experimental")]
405 pub memory_cache: Option<bool>,
407
408 #[cfg(windows)]
409 pub windows_service: Option<bool>,
411}
412
413#[derive(Debug, Serialize, Deserialize, Clone)]
415#[serde(rename_all = "kebab-case")]
416pub struct Settings {
417 pub general: Option<General>,
419 pub advanced: Option<Advanced>,
421}
422
423impl Settings {
424 pub fn read(config_file: &Path) -> Result<Settings> {
426 let ext = config_file.extension();
428 if ext.is_none() || ext.unwrap().is_empty() || ext.unwrap().ne("toml") {
429 bail!("configuration file should be in toml format. E.g `sws.toml`");
430 }
431
432 let toml =
434 read_toml_file(config_file).with_context(|| "error reading toml configuration file")?;
435 let mut unused = BTreeSet::new();
436 let manifest: Settings = serde_ignored::deserialize(toml, |path| {
437 let mut key = String::new();
438 helpers::stringify(&mut key, &path);
439 unused.insert(key);
440 })
441 .with_context(|| "error during toml configuration file deserialization")?;
442
443 for key in unused {
444 println!("Warning: unused configuration manifest key \"{key}\" or unsupported");
445 }
446
447 Ok(manifest)
448 }
449}
450
451fn read_toml_file(path: &Path) -> Result<toml::Value> {
453 let toml_str = helpers::read_file(path).with_context(|| {
454 format!(
455 "error trying to deserialize toml configuration file at \"{}\"",
456 path.display()
457 )
458 })?;
459 toml_str
460 .parse()
461 .map_err(|e| anyhow::Error::from(e).context("could not parse input as TOML"))
462}