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
18use crate::{helpers, Context, Result};
19
20#[derive(Debug, Serialize, Deserialize, Clone)]
21#[serde(rename_all = "kebab-case")]
22pub enum LogLevel {
24 Error,
26 Warn,
28 Info,
30 Debug,
32 Trace,
34}
35
36impl LogLevel {
37 pub fn name(&self) -> &'static str {
39 match self {
40 LogLevel::Error => "error",
41 LogLevel::Warn => "warn",
42 LogLevel::Info => "info",
43 LogLevel::Debug => "debug",
44 LogLevel::Trace => "trace",
45 }
46 }
47}
48
49#[cfg(any(
50 feature = "compression",
51 feature = "compression-gzip",
52 feature = "compression-brotli",
53 feature = "compression-zstd",
54 feature = "compression-deflate"
55))]
56#[cfg_attr(
57 docsrs,
58 doc(cfg(any(
59 feature = "compression",
60 feature = "compression-gzip",
61 feature = "compression-brotli",
62 feature = "compression-zstd",
63 feature = "compression-deflate"
64 )))
65)]
66#[derive(clap::ValueEnum, Debug, Serialize, Deserialize, Copy, Clone)]
67#[serde(rename_all = "kebab-case")]
68pub enum CompressionLevel {
70 Fastest,
72 Best,
74 Default,
76}
77
78#[cfg(any(
79 feature = "compression",
80 feature = "compression-gzip",
81 feature = "compression-brotli",
82 feature = "compression-zstd",
83 feature = "compression-deflate"
84))]
85#[cfg_attr(
86 docsrs,
87 doc(cfg(any(
88 feature = "compression",
89 feature = "compression-gzip",
90 feature = "compression-brotli",
91 feature = "compression-zstd",
92 feature = "compression-deflate"
93 )))
94)]
95impl CompressionLevel {
96 pub(crate) fn into_algorithm_level(self, default: i32) -> async_compression::Level {
99 match self {
100 Self::Fastest => async_compression::Level::Fastest,
101 Self::Best => async_compression::Level::Best,
102 Self::Default => async_compression::Level::Precise(default),
103 }
104 }
105}
106
107#[derive(Debug, Serialize, Deserialize, Clone)]
108#[serde(rename_all = "kebab-case")]
109pub struct Headers {
111 pub source: String,
113 #[serde(rename(deserialize = "headers"), with = "http_serde::header_map")]
114 pub headers: HeaderMap,
116}
117
118#[derive(Debug, Serialize_repr, Deserialize_repr, Clone)]
119#[repr(u16)]
120pub enum RedirectsKind {
122 Permanent = 301,
124 Temporary = 302,
126}
127
128#[derive(Debug, Serialize, Deserialize, Clone)]
129#[serde(rename_all = "kebab-case")]
130pub struct Redirects {
132 pub host: Option<String>,
134 pub source: String,
136 pub destination: String,
138 pub kind: RedirectsKind,
140}
141
142#[derive(Debug, Serialize, Deserialize, Clone)]
143#[serde(rename_all = "kebab-case")]
144pub struct Rewrites {
146 pub source: String,
148 pub destination: String,
150 pub redirect: Option<RedirectsKind>,
152}
153
154#[derive(Debug, Serialize, Deserialize, Clone)]
155#[serde(rename_all = "kebab-case")]
156pub struct VirtualHosts {
158 pub host: String,
160 pub root: Option<PathBuf>,
162}
163
164#[cfg(feature = "experimental")]
165#[derive(Debug, Serialize, Deserialize, Clone)]
166#[serde(rename_all = "kebab-case")]
167pub struct MemoryCache {
169 pub capacity: Option<u64>,
171 pub ttl: Option<u64>,
173 pub tti: Option<u64>,
175 pub max_file_size: Option<u64>,
177}
178
179#[derive(Debug, Serialize, Deserialize, Clone)]
181#[serde(rename_all = "kebab-case")]
182pub struct Advanced {
183 pub headers: Option<Vec<Headers>>,
185 pub rewrites: Option<Vec<Rewrites>>,
187 pub redirects: Option<Vec<Redirects>>,
189 pub virtual_hosts: Option<Vec<VirtualHosts>>,
191 #[cfg(feature = "experimental")]
192 pub memory_cache: Option<MemoryCache>,
194}
195
196#[derive(Debug, Serialize, Deserialize, Clone)]
199#[serde(rename_all = "kebab-case")]
200pub struct General {
201 pub host: Option<String>,
203 pub port: Option<u16>,
205 pub root: Option<PathBuf>,
207
208 pub log_level: Option<LogLevel>,
210
211 pub cache_control_headers: Option<bool>,
213
214 #[cfg(any(
216 feature = "compression",
217 feature = "compression-gzip",
218 feature = "compression-brotli",
219 feature = "compression-zstd",
220 feature = "compression-deflate"
221 ))]
222 #[cfg_attr(
223 docsrs,
224 doc(cfg(any(
225 feature = "compression",
226 feature = "compression-gzip",
227 feature = "compression-brotli",
228 feature = "compression-zstd",
229 feature = "compression-deflate"
230 )))
231 )]
232 pub compression: Option<bool>,
233
234 #[cfg(any(
236 feature = "compression",
237 feature = "compression-gzip",
238 feature = "compression-brotli",
239 feature = "compression-zstd",
240 feature = "compression-deflate"
241 ))]
242 #[cfg_attr(
243 docsrs,
244 doc(cfg(any(
245 feature = "compression",
246 feature = "compression-gzip",
247 feature = "compression-brotli",
248 feature = "compression-zstd",
249 feature = "compression-deflate"
250 )))
251 )]
252 pub compression_level: Option<CompressionLevel>,
253
254 #[cfg(any(
256 feature = "compression",
257 feature = "compression-gzip",
258 feature = "compression-brotli",
259 feature = "compression-zstd",
260 feature = "compression-deflate"
261 ))]
262 #[cfg_attr(
263 docsrs,
264 doc(cfg(any(
265 feature = "compression",
266 feature = "compression-gzip",
267 feature = "compression-brotli",
268 feature = "compression-zstd",
269 feature = "compression-deflate"
270 )))
271 )]
272 pub compression_static: Option<bool>,
273
274 pub page404: Option<PathBuf>,
276 pub page50x: Option<PathBuf>,
278
279 #[cfg(feature = "http2")]
281 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
282 pub http2: Option<bool>,
283 #[cfg(feature = "http2")]
285 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
286 pub http2_tls_cert: Option<PathBuf>,
287 #[cfg(feature = "http2")]
289 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
290 pub http2_tls_key: Option<PathBuf>,
291
292 #[cfg(feature = "http2")]
294 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
295 pub https_redirect: Option<bool>,
296 #[cfg(feature = "http2")]
298 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
299 pub https_redirect_host: Option<String>,
300 #[cfg(feature = "http2")]
302 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
303 pub https_redirect_from_port: Option<u16>,
304 #[cfg(feature = "http2")]
306 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
307 pub https_redirect_from_hosts: Option<String>,
308
309 pub security_headers: Option<bool>,
311
312 pub cors_allow_origins: Option<String>,
314 pub cors_allow_headers: Option<String>,
316 pub cors_expose_headers: Option<String>,
318
319 pub index_files: Option<String>,
321
322 #[cfg(feature = "directory-listing")]
324 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
325 pub directory_listing: Option<bool>,
326 #[cfg(feature = "directory-listing")]
328 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
329 pub directory_listing_order: Option<u8>,
330 #[cfg(feature = "directory-listing")]
332 #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
333 pub directory_listing_format: Option<DirListFmt>,
334
335 #[cfg(feature = "basic-auth")]
337 #[cfg_attr(docsrs, doc(cfg(feature = "basic-auth")))]
338 pub basic_auth: Option<String>,
339
340 pub fd: Option<usize>,
342
343 pub threads_multiplier: Option<usize>,
345
346 pub max_blocking_threads: Option<usize>,
348
349 pub grace_period: Option<u8>,
351
352 #[cfg(feature = "fallback-page")]
354 #[cfg_attr(docsrs, doc(cfg(feature = "fallback-page")))]
355 pub page_fallback: Option<PathBuf>,
356
357 pub log_remote_address: Option<bool>,
359
360 pub log_x_real_ip: Option<bool>,
362
363 pub log_forwarded_for: Option<bool>,
365
366 pub trusted_proxies: Option<Vec<IpAddr>>,
368
369 pub redirect_trailing_slash: Option<bool>,
371
372 pub ignore_hidden_files: Option<bool>,
374
375 pub disable_symlinks: Option<bool>,
377
378 pub health: Option<bool>,
380
381 #[cfg(all(unix, feature = "experimental"))]
382 pub experimental_metrics: Option<bool>,
384
385 pub maintenance_mode: Option<bool>,
387
388 pub maintenance_mode_status: Option<u16>,
390
391 pub maintenance_mode_file: Option<PathBuf>,
393
394 #[cfg(feature = "experimental")]
395 pub memory_cache: Option<bool>,
397
398 #[cfg(windows)]
399 pub windows_service: Option<bool>,
401}
402
403#[derive(Debug, Serialize, Deserialize, Clone)]
405#[serde(rename_all = "kebab-case")]
406pub struct Settings {
407 pub general: Option<General>,
409 pub advanced: Option<Advanced>,
411}
412
413impl Settings {
414 pub fn read(config_file: &Path) -> Result<Settings> {
416 let ext = config_file.extension();
418 if ext.is_none() || ext.unwrap().is_empty() || ext.unwrap().ne("toml") {
419 bail!("configuration file should be in toml format. E.g `config.toml`");
420 }
421
422 let toml =
424 read_toml_file(config_file).with_context(|| "error reading toml configuration file")?;
425 let mut unused = BTreeSet::new();
426 let manifest: Settings = serde_ignored::deserialize(toml, |path| {
427 let mut key = String::new();
428 helpers::stringify(&mut key, &path);
429 unused.insert(key);
430 })
431 .with_context(|| "error during toml configuration file deserialization")?;
432
433 for key in unused {
434 println!("Warning: unused configuration manifest key \"{key}\" or unsupported");
435 }
436
437 Ok(manifest)
438 }
439}
440
441fn read_toml_file(path: &Path) -> Result<toml::Value> {
443 let toml_str = helpers::read_file(path).with_context(|| {
444 format!(
445 "error trying to deserialize toml configuration file at \"{}\"",
446 path.display()
447 )
448 })?;
449 toml_str
450 .parse()
451 .map_err(|e| anyhow::Error::from(e).context("could not parse input as TOML"))
452}