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}