Skip to main content

ravenrs_report_server/
config.rs

1//! The server's configuration. Secrets are never in the file: it names the
2//! environment variables that hold them.
3
4use std::collections::BTreeMap;
5use std::path::{Path, PathBuf};
6
7use serde::Deserialize;
8
9/// An API token shorter than this is refused at startup.
10const MIN_TOKEN_LENGTH: usize = 16;
11
12/// The server's configuration, read from a JSON file.
13#[derive(Clone, Debug, Deserialize)]
14#[serde(rename_all = "camelCase", deny_unknown_fields)]
15pub struct Config {
16    /// Address and port to listen on; the loopback interface by default.
17    #[serde(default = "default_listen")]
18    pub listen: String,
19    /// Where layouts, jobs, history and documents are kept.
20    pub data_dir: PathBuf,
21    /// The environment variable holding the API token.
22    #[serde(default = "default_token_env")]
23    pub api_token_env: String,
24    /// The folder under which `folder` deliveries write; such deliveries are refused without it.
25    #[serde(default)]
26    pub output_dir: Option<PathBuf>,
27    /// The mail server; `email` deliveries are refused without it.
28    #[serde(default)]
29    pub smtp: Option<SmtpConfig>,
30    /// The hosts `url` data sources may read from. Any other host is refused.
31    #[serde(default)]
32    pub data_hosts: Vec<DataHost>,
33    /// How many reports render at the same time.
34    #[serde(default = "default_concurrency")]
35    pub max_concurrent_renders: usize,
36    /// The wait before the first retry of a failed run; it doubles on each retry.
37    #[serde(default = "default_retry_delay")]
38    pub retry_delay_seconds: u64,
39    /// A run this late (the server was down) still happens; a later one is recorded as missed.
40    #[serde(default = "default_misfire_grace")]
41    pub misfire_grace_minutes: u64,
42    /// How many runs of each job the history keeps.
43    #[serde(default = "default_history")]
44    pub history_limit: usize,
45    /// The largest request body accepted, in megabytes.
46    #[serde(default = "default_body_limit")]
47    pub max_body_megabytes: usize,
48}
49
50/// How to reach the mail server.
51#[derive(Clone, Debug, Deserialize)]
52#[serde(rename_all = "camelCase", deny_unknown_fields)]
53pub struct SmtpConfig {
54    /// Host name.
55    pub host: String,
56    /// Port; the usual one for `security` when absent.
57    #[serde(default)]
58    pub port: Option<u16>,
59    /// How the connection is protected.
60    #[serde(default)]
61    pub security: SmtpSecurity,
62    /// The sender of every message, such as `Reports <reports@example.com>`.
63    pub from: String,
64    /// The environment variable holding the user name, when the server asks for one.
65    #[serde(default)]
66    pub username_env: Option<String>,
67    /// The environment variable holding the password.
68    #[serde(default)]
69    pub password_env: Option<String>,
70}
71
72/// How the connection to the mail server is protected.
73#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Deserialize)]
74#[serde(rename_all = "camelCase")]
75pub enum SmtpSecurity {
76    /// Upgrades to TLS, and fails if the server cannot (port 587).
77    #[default]
78    Starttls,
79    /// TLS from the start (port 465).
80    Tls,
81    /// No encryption: only for a relay on the same machine or a local test server.
82    None,
83}
84
85/// A host `url` data sources may read from.
86#[derive(Clone, Debug, Deserialize)]
87#[serde(rename_all = "camelCase", deny_unknown_fields)]
88pub struct DataHost {
89    /// The host name, exactly as in the URL.
90    pub host: String,
91    /// Headers sent to this host: header name to the environment variable holding its value.
92    #[serde(default)]
93    pub headers_env: BTreeMap<String, String>,
94}
95
96fn default_listen() -> String {
97    "127.0.0.1:8090".to_owned()
98}
99
100fn default_token_env() -> String {
101    "RAVENRS_SERVER_TOKEN".to_owned()
102}
103
104fn default_concurrency() -> usize {
105    4
106}
107
108fn default_retry_delay() -> u64 {
109    60
110}
111
112fn default_misfire_grace() -> u64 {
113    60
114}
115
116fn default_history() -> usize {
117    1000
118}
119
120fn default_body_limit() -> usize {
121    32
122}
123
124impl Config {
125    /// A configuration with every default, keeping its data in `data_dir`.
126    pub fn new(data_dir: impl Into<PathBuf>) -> Self {
127        Self {
128            listen: default_listen(),
129            data_dir: data_dir.into(),
130            api_token_env: default_token_env(),
131            output_dir: None,
132            smtp: None,
133            data_hosts: Vec::new(),
134            max_concurrent_renders: default_concurrency(),
135            retry_delay_seconds: default_retry_delay(),
136            misfire_grace_minutes: default_misfire_grace(),
137            history_limit: default_history(),
138            max_body_megabytes: default_body_limit(),
139        }
140    }
141
142    /// Reads the configuration from a JSON file.
143    pub fn load(path: &Path) -> Result<Self, String> {
144        let text = std::fs::read_to_string(path).map_err(|e| format!("{}: {e}", path.display()))?;
145        serde_json::from_str(&text).map_err(|e| format!("{}: {e}", path.display()))
146    }
147
148    /// The API token, from the environment. There is no way to run without one.
149    pub fn api_token(&self) -> Result<String, String> {
150        let token = std::env::var(&self.api_token_env)
151            .map_err(|_| format!("set the API token in the environment variable {}", self.api_token_env))?;
152        if token.len() < MIN_TOKEN_LENGTH {
153            return Err(format!("the API token must have at least {MIN_TOKEN_LENGTH} characters"));
154        }
155        Ok(token)
156    }
157}