From 4336f47c4f7f11668ab97583e91b622a664ad39f Mon Sep 17 00:00:00 2001 From: 81reap Date: Tue, 18 Aug 2026 00:55:31 -0400 Subject: [PATCH] fix(config) :: document the real configuration defaults' --- CHANGELOG.md | 1 + configuration.md | 12 +++--- src/app_config.rs | 5 +-- tests/configuration_docs/mod.rs | 70 +++++++++++++++++++++++++++++++++ tests/mod.rs | 1 + 5 files changed, 80 insertions(+), 9 deletions(-) create mode 100644 tests/configuration_docs/mod.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 64a4ca05..893c789c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - `column` charts now display vertical bars instead of nothing at all. - `stacked` is now ignored on chart types that cannot stack, instead of displaying an empty chart. - Screen readers now announce the title of the modal component instead of an unnamed dialog. + - The configuration guide now documents the defaults of `listen_on`, `port`, `database_url`, `max_database_pool_connections`, `https_certificate_cache_dir`, `system_root_ca_certificates` and `max_recursion_depth`. ## v0.45 diff --git a/configuration.md b/configuration.md index 2d5c3450..db68727c 100644 --- a/configuration.md +++ b/configuration.md @@ -8,13 +8,13 @@ Here are the available configuration options and their default values: | variable | default | description | | --------------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `listen_on` | 0.0.0.0:8080 | Interface and port on which the web server should listen | -| `database_url` | `sqlite://sqlpage.db?mode=rwc` or `DSN=DuckDB` | Database connection URL, either `dbengine://user:password@host:port/dbname` or an ODBC connection string. Special characters should be [percent-encoded](https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding). See [Database connection strings](#database-connection-strings) for details and examples.| +| `listen_on` | 0.0.0.0:8080, or 0.0.0.0:443 when `https_domain` is set | Interface and port on which the web server should listen | +| `database_url` | `sqlite://sqlpage.db?mode=rwc` in the `configuration_directory` | Database connection URL, either `dbengine://user:password@host:port/dbname` or an ODBC connection string. Special characters should be [percent-encoded](https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding). See [Database connection strings](#database-connection-strings) for details and examples. When left unset, SQLPage looks for an existing db or creates `sqlpage.db` in the `configuration_directory`, with a fallback to in-memory if unwritable.| | `database_password` | | Database password. If set, this will override any password specified in the `database_url`. This allows you to keep the password separate from the connection string for better security. | -| `port` | 8080 | Like listen_on, but specifies only the port. | +| `port` | 8080, or 443 when `https_domain` is set | Like listen_on, but specifies only the port. | | `unix_socket` | | Path to a UNIX socket to listen on instead of the TCP port. If specified, SQLPage will accept HTTP connections only on this socket and not on any TCP port. This option is mutually exclusive with `listen_on` and `port`. | `host` | | The web address where your application is accessible (e.g., "myapp.example.com"). Used for login redirects with OIDC. | -| `max_database_pool_connections` | PostgreSQL: 50
MySql: 75
SQLite: 16
MSSQL: 100 | How many simultaneous database connections to open at most | +| `max_database_pool_connections` | PostgreSQL: 50
MySql: 75
SQLite: 16, or 128 in memory
MSSQL: 100
ODBC: 50 | How many simultaneous database connections to open at most | | `database_connection_idle_timeout_seconds` | SQLite: None
All other: 30 minutes | Automatically close database connections after this period of inactivity. Set to 0 to disable. | | `database_connection_max_lifetime_seconds` | SQLite: None
All other: 60 minutes | Always close database connections after this amount of time. Set to 0 to disable. | | `database_connection_retries` | 6 | Database connection attempts before giving up. Retries will happen every 5 seconds. | @@ -36,7 +36,7 @@ Here are the available configuration options and their default values: | `compress_responses` | false | When the client supports it, compress the http response body. This can save bandwidth and speed up page loading on slow connections, but can also increase CPU usage and cause rendering delays on pages that take time to render (because streaming responses are buffered for longer than necessary). | | `https_domain` | | Domain name to request a certificate for. Setting this parameter will automatically make SQLPage listen on port 443 and request an SSL certificate. The server will take a little bit longer to start the first time it has to request a certificate. | | `https_certificate_email` | contact@ | The email address to use when requesting a certificate. | -| `https_certificate_cache_dir` | ./sqlpage/https | A writeable directory where to cache the certificates, so that SQLPage can serve https traffic immediately when it restarts. | +| `https_certificate_cache_dir` | ./sqlpage/https | A writeable directory where to cache the certificates, so that SQLPage can serve https traffic immediately when it restarts. The default ignores `web_root` and `configuration_directory`. | | `https_acme_directory_url` | https://acme-v02.api.letsencrypt.org/directory | The URL of the ACME directory to use when requesting a certificate. | | `environment` | development | The environment in which SQLPage is running. Can be either `development` or `production`. In `production` mode, SQLPage will hide error messages and stack traces from the user, and will cache sql files in memory to avoid reloading them from disk. | | `cache_stale_duration_ms` | 1000 (prod), 0 (dev) | The duration in milliseconds that a file can be cached before its freshness is checked against the filesystem. Defaults to 1000ms (1 second) in production and 0ms in development. | @@ -49,7 +49,7 @@ Here are the available configuration options and their default values: | `smtp_tls_mode` | `starttls` | Encryption mode for `sqlpage.send_mail`: `starttls` requires a STARTTLS upgrade, `tls` uses TLS from connection start, and `none` permits plaintext only without credentials for trusted local SMTP servers. | | `max_email_attachment_size` | 10485760 | Maximum combined decoded size, in bytes, of all attachments in one email. Defaults to 10 MiB. This is independent of `max_uploaded_file_size` because attachments may come from sources other than form uploads. | | `system_root_ca_certificates` | false | Whether to use the system root CA certificates to validate SSL certificates when making http requests with `sqlpage.fetch`. If set to false, SQLPage will use its own set of root CA certificates. If the `SSL_CERT_FILE` or `SSL_CERT_DIR` environment variables are set, they will be used instead of the system root CA certificates. | -| `max_recursion_depth` | 10 | Maximum depth of recursion allowed in the `run_sql` function. Maximum value is 255. | +| `max_recursion_depth` | 10 | Maximum depth of recursion allowed in the `run_sql` function. Values above 255 stop the server from starting. | | `markdown_allow_dangerous_html` | false | Whether to allow raw HTML in markdown content. Only enable this if the markdown content is fully trusted (not user generated). | | `markdown_allow_dangerous_protocol` | false | Whether to allow dangerous protocols (like javascript:) in markdown links. Only enable this if the markdown content is fully trusted (not user generated). | diff --git a/src/app_config.rs b/src/app_config.rs index 7569a018..7310b19c 100644 --- a/src/app_config.rs +++ b/src/app_config.rs @@ -287,7 +287,7 @@ pub struct AppConfig { #[serde(default = "default_max_email_attachment_size")] pub max_email_attachment_size: usize, - /// Maximum size of uploaded files in bytes. The default is 10MiB (10 * 1024 * 1024 bytes) + /// Maximum size of uploaded files in bytes. The default is 5MiB (5 * 1024 * 1024 bytes) #[serde(default = "default_max_file_size")] pub max_uploaded_file_size: usize, @@ -727,8 +727,7 @@ fn default_compress_responses() -> bool { } fn default_system_root_ca_certificates() -> bool { - std::env::var("SSL_CERT_FILE").is_ok_and(|x| !x.is_empty()) - || std::env::var("SSL_CERT_DIR").is_ok_and(|x| !x.is_empty()) + crate::webserver::http_client::default_system_root_ca_certificates_from_env() } fn default_max_recursion_depth() -> u8 { diff --git a/tests/configuration_docs/mod.rs b/tests/configuration_docs/mod.rs new file mode 100644 index 00000000..244b07e6 --- /dev/null +++ b/tests/configuration_docs/mod.rs @@ -0,0 +1,70 @@ +use serde::Deserialize; +use serde::de::value::Error as DeError; +use serde::de::{Deserializer, Visitor}; +use sqlpage::app_config::AppConfig; +use std::collections::BTreeSet; + +#[test] +fn configuration_md_documents_every_option() { + let documented = documented_option_names(); + let implemented = app_config_option_names(); + + let undocumented: Vec<_> = implemented.difference(&documented).collect(); + let unimplemented: Vec<_> = documented.difference(&implemented).collect(); + + assert!( + undocumented.is_empty() && unimplemented.is_empty(), + "configuration.md and AppConfig disagree.\n\ + Missing from configuration.md: {undocumented:?}\n\ + Documented but not in AppConfig: {unimplemented:?}" + ); +} + +fn documented_option_names() -> BTreeSet { + let table = std::fs::read_to_string("configuration.md").unwrap(); + table + .lines() + .filter_map(|line| line.strip_prefix("| `")?.split('`').next()) + .map(str::to_owned) + .collect() +} + +fn app_config_option_names() -> BTreeSet { + let mut collector = OptionNameCollector::default(); + let _ = AppConfig::deserialize(&mut collector); + collector + .names + .iter() + .map(|name| (*name).to_owned()) + .collect() +} + +/// Serde hands the names it expects, renames included, to `deserialize_struct`. +#[derive(Default)] +struct OptionNameCollector { + names: &'static [&'static str], +} + +impl<'de> Deserializer<'de> for &mut OptionNameCollector { + type Error = DeError; + + fn deserialize_struct>( + self, + _name: &'static str, + fields: &'static [&'static str], + _visitor: V, + ) -> Result { + self.names = fields; + Err(serde::de::Error::custom("names collected")) + } + + fn deserialize_any>(self, _visitor: V) -> Result { + Err(serde::de::Error::custom("AppConfig is not a struct")) + } + + serde::forward_to_deserialize_any! { + bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes byte_buf + option unit unit_struct newtype_struct seq tuple tuple_struct map enum + identifier ignored_any + } +} diff --git a/tests/mod.rs b/tests/mod.rs index 9fe58d3b..2084e691 100644 --- a/tests/mod.rs +++ b/tests/mod.rs @@ -1,5 +1,6 @@ mod basic; mod common; +mod configuration_docs; mod core; mod data_formats; mod errors;