backend/config/
app.rs

1//! Configuration of the server.
2
3use dotenvy::dotenv;
4use reqwest::Url;
5use secrecy::Secret;
6use serde::Deserialize;
7
8/// Environment variables that are required for the configuration of the server.
9#[derive(Deserialize)]
10struct EnvVars {
11    /// The host the server should be started on.
12    pub bind_address_host: String,
13    /// The port the server should be started on.
14    pub bind_address_port: u16,
15    /// The connection string for the database.
16    pub database_url: String,
17    /// The host of the authentication server.
18    pub auth_host: String,
19    /// The `client_id` the frontend should use to log in its users.
20    pub auth_client_id: String,
21    /// The `client_id` the backend uses to communicate with the auth server.
22    pub auth_admin_client_id: Option<String>,
23    /// The `client_secret` the backend uses to communicate with the auth server.
24    pub auth_admin_client_secret: Option<String>,
25}
26
27/// Configuration data for the server.
28#[derive(Debug)]
29pub struct Config {
30    /// The address and port the server should be started on.
31    pub bind_address: (String, u16),
32    /// The location of the database as a URL.
33    pub database_url: Secret<String>,
34    /// The discovery URI of the server that issues tokens.
35    ///
36    /// Can be used to fetch other relevant URLs such as the `jwks_uri` or the `token_endpoint`.
37    pub auth_discovery_uri: Url,
38    /// The `client_id` the frontend should use to log in its users.
39    pub client_id: String,
40
41    /// The URI of the auth server used to acquire a token.
42    pub auth_token_uri: Url,
43    /// The `client_id` the backend uses to communicate with the auth server.
44    pub auth_admin_client_id: Option<String>,
45    /// The `client_secret` the backend uses to communicate with the auth server.
46    pub auth_admin_client_secret: Option<Secret<String>>,
47}
48
49impl Config {
50    /// Load the configuration using environment variables.
51    ///
52    /// # Errors
53    /// * If the .env file is present, but there was an error loading it.
54    /// * If an environment variable is missing.
55    /// * If a variable could not be parsed correctly.
56    pub fn from_env() -> Result<Self, Box<dyn std::error::Error>> {
57        load_env_file()?;
58        let env: EnvVars = envy::from_env()?;
59
60        let auth_discovery_uri_str = format!(
61            "{}/realms/PermaplanT/.well-known/openid-configuration",
62            env.auth_host
63        );
64        let auth_discovery_uri = auth_discovery_uri_str.parse::<Url>().map_err(|e| {
65            format!("Failed to parse auth_discovery_uri: {e} (uri: {auth_discovery_uri_str})")
66        })?;
67        let auth_token_uri_str = format!(
68            "{}/realms/master/protocol/openid-connect/token",
69            env.auth_host
70        );
71        let auth_token_uri = auth_token_uri_str
72            .parse::<Url>()
73            .map_err(|e| format!("Failed to parse auth_token_uri: {e}"))?;
74
75        Ok(Self {
76            bind_address: (env.bind_address_host, env.bind_address_port),
77            database_url: Secret::new(env.database_url),
78            auth_discovery_uri,
79            client_id: env.auth_client_id,
80            auth_token_uri,
81            auth_admin_client_id: env.auth_admin_client_id,
82            auth_admin_client_secret: env.auth_admin_client_secret.map(Secret::new),
83        })
84    }
85}
86
87/// Load the .env file. A missing file does not result in an error.
88///
89/// # Errors
90/// * If the .env file is present, but there was an error loading it.
91fn load_env_file() -> Result<(), Box<dyn std::error::Error>> {
92    match dotenv() {
93        Err(e) if e.not_found() => Ok(()), // missing .env is ok
94        Err(e) => Err(e.into()),           // any other errors are a problem
95        _ => Ok(()),
96    }
97}