|
1 | 1 | use serde::Deserialize; |
| 2 | +use std::net::SocketAddr; |
2 | 3 |
|
3 | | -#[derive(Deserialize)] |
4 | | -pub(crate) struct Config { |
5 | | - pub(crate) server_config: ServerConfig, |
6 | | - pub(crate) jwt_auth_config: Option<JwtAuthConfig>, |
7 | | - pub(crate) postgresql_config: Option<PostgreSQLConfig>, |
| 4 | +const BIND_ADDR_VAR: &str = "VSS_BIND_ADDRESS"; |
| 5 | +const JWT_RSA_PEM_VAR: &str = "VSS_JWT_RSA_PEM"; |
| 6 | +const PSQL_USER_VAR: &str = "VSS_PSQL_USERNAME"; |
| 7 | +const PSQL_PASS_VAR: &str = "VSS_PSQL_PASSWORD"; |
| 8 | +const PSQL_ADDR_VAR: &str = "VSS_PSQL_ADDRESS"; |
| 9 | +const PSQL_DB_VAR: &str = "VSS_PSQL_DEFAULT_DB"; |
| 10 | +const PSQL_VSS_DB_VAR: &str = "VSS_PSQL_VSS_DB"; |
| 11 | +const PSQL_TLS_VAR: &str = "VSS_PSQL_TLS"; |
| 12 | +const PSQL_CERT_PEM_VAR: &str = "VSS_PSQL_CRT_PEM"; |
| 13 | + |
| 14 | +// The structure of the toml config file. Any settings specified therein can be overriden by the corresponding |
| 15 | +// environment variable. |
| 16 | +#[derive(Deserialize, Default)] |
| 17 | +struct TomlConfig { |
| 18 | + server_config: Option<ServerConfig>, |
| 19 | + jwt_auth_config: Option<JwtAuthConfig>, |
| 20 | + postgresql_config: Option<PostgreSQLConfig>, |
8 | 21 | } |
9 | 22 |
|
10 | 23 | #[derive(Deserialize)] |
11 | | -pub(crate) struct ServerConfig { |
12 | | - pub(crate) host: String, |
13 | | - pub(crate) port: u16, |
| 24 | +struct ServerConfig { |
| 25 | + bind_address: Option<SocketAddr>, |
14 | 26 | } |
15 | 27 |
|
16 | 28 | #[derive(Deserialize)] |
17 | | -pub(crate) struct JwtAuthConfig { |
18 | | - pub(crate) rsa_pem: Option<String>, |
| 29 | +struct JwtAuthConfig { |
| 30 | + rsa_pem: Option<String>, |
19 | 31 | } |
20 | 32 |
|
21 | 33 | #[derive(Deserialize)] |
22 | | -pub(crate) struct PostgreSQLConfig { |
23 | | - pub(crate) username: Option<String>, // Optional in TOML, can be overridden by env |
24 | | - pub(crate) password: Option<String>, // Optional in TOML, can be overridden by env |
25 | | - pub(crate) host: String, |
26 | | - pub(crate) port: u16, |
27 | | - pub(crate) default_database: String, |
28 | | - pub(crate) vss_database: String, |
29 | | - pub(crate) tls: Option<TlsConfig>, |
| 34 | +struct PostgreSQLConfig { |
| 35 | + username: Option<String>, |
| 36 | + password: Option<String>, |
| 37 | + address: Option<SocketAddr>, |
| 38 | + default_database: Option<String>, |
| 39 | + vss_database: Option<String>, |
| 40 | + tls: Option<TlsConfig>, |
30 | 41 | } |
31 | 42 |
|
32 | 43 | #[derive(Deserialize)] |
33 | | -pub(crate) struct TlsConfig { |
34 | | - pub(crate) crt_pem: Option<String>, |
35 | | -} |
36 | | - |
37 | | -impl PostgreSQLConfig { |
38 | | - pub(crate) fn to_postgresql_endpoint(&self) -> String { |
39 | | - let username_env = std::env::var("VSS_POSTGRESQL_USERNAME"); |
40 | | - let username = username_env.as_ref() |
41 | | - .ok() |
42 | | - .or_else(|| self.username.as_ref()) |
43 | | - .expect("PostgreSQL database username must be provided in config or env var VSS_POSTGRESQL_USERNAME must be set."); |
44 | | - let password_env = std::env::var("VSS_POSTGRESQL_PASSWORD"); |
45 | | - let password = password_env.as_ref() |
46 | | - .ok() |
47 | | - .or_else(|| self.password.as_ref()) |
48 | | - .expect("PostgreSQL database password must be provided in config or env var VSS_POSTGRESQL_PASSWORD must be set."); |
49 | | - |
50 | | - format!("postgresql://{}:{}@{}:{}", username, password, self.host, self.port) |
| 44 | +struct TlsConfig { |
| 45 | + crt_pem: Option<String>, |
| 46 | +} |
| 47 | + |
| 48 | +// Encapsulates the result of reading both the environment variables and the config file. |
| 49 | +pub(crate) struct Configuration { |
| 50 | + pub(crate) bind_address: SocketAddr, |
| 51 | + pub(crate) rsa_pem: Option<String>, |
| 52 | + pub(crate) postgresql_prefix: String, |
| 53 | + pub(crate) default_db: String, |
| 54 | + pub(crate) vss_db: String, |
| 55 | + pub(crate) tls_config: Option<Option<String>>, |
| 56 | +} |
| 57 | + |
| 58 | +#[inline] |
| 59 | +fn read_env(env_var: &str) -> Result<Option<String>, String> { |
| 60 | + match std::env::var(env_var) { |
| 61 | + Ok(env) => Ok(Some(env)), |
| 62 | + Err(std::env::VarError::NotPresent) => Ok(None), |
| 63 | + Err(e) => Err(format!("Failed to load the {} environment variable: {}", env_var, e)), |
51 | 64 | } |
52 | 65 | } |
53 | 66 |
|
54 | | -pub(crate) fn load_config(config_path: &str) -> Result<Config, Box<dyn std::error::Error>> { |
55 | | - let config_str = std::fs::read_to_string(config_path)?; |
56 | | - let config: Config = toml::from_str(&config_str)?; |
57 | | - Ok(config) |
| 67 | +#[inline] |
| 68 | +fn read_config<'a, T: std::fmt::Display>( |
| 69 | + env: Option<T>, config: Option<T>, item: &str, var_name: &str, |
| 70 | +) -> Result<T, String> { |
| 71 | + env.or(config).ok_or(format!( |
| 72 | + "{} must be provided in the configuration file or the environment variable {} must be set.", |
| 73 | + item, var_name |
| 74 | + )) |
| 75 | +} |
| 76 | + |
| 77 | +pub(crate) fn load_configuration(config_file_path: Option<&str>) -> Result<Configuration, String> { |
| 78 | + let TomlConfig { server_config, jwt_auth_config, postgresql_config } = match config_file_path { |
| 79 | + Some(path) => { |
| 80 | + let config_file = std::fs::read_to_string(path) |
| 81 | + .map_err(|e| format!("Failed to read configuration file: {}", e))?; |
| 82 | + toml::from_str(&config_file) |
| 83 | + .map_err(|e| format!("Failed to parse configuration file: {}", e))? |
| 84 | + }, |
| 85 | + None => TomlConfig::default(), // All fields are set to `None` |
| 86 | + }; |
| 87 | + |
| 88 | + let bind_address_env = read_env(BIND_ADDR_VAR)? |
| 89 | + .map(|addr| { |
| 90 | + addr.parse().map_err(|e| { |
| 91 | + format!("Unable to parse the bind address environment variable: {}", e) |
| 92 | + }) |
| 93 | + }) |
| 94 | + .transpose()?; |
| 95 | + let bind_address = read_config( |
| 96 | + bind_address_env, |
| 97 | + server_config.and_then(|c| c.bind_address), |
| 98 | + "VSS server bind address", |
| 99 | + BIND_ADDR_VAR, |
| 100 | + )?; |
| 101 | + |
| 102 | + let rsa_pem_env = read_env(JWT_RSA_PEM_VAR)?; |
| 103 | + let rsa_pem = rsa_pem_env.or(jwt_auth_config.and_then(|config| config.rsa_pem)); |
| 104 | + |
| 105 | + let username_env = read_env(PSQL_USER_VAR)?; |
| 106 | + let password_env = read_env(PSQL_PASS_VAR)?; |
| 107 | + let address_env: Option<SocketAddr> = read_env(PSQL_ADDR_VAR)? |
| 108 | + .map(|address| { |
| 109 | + address.parse().map_err(|e| { |
| 110 | + format!("Unable to parse the postgresql address environment variable: {}", e) |
| 111 | + }) |
| 112 | + }) |
| 113 | + .transpose()?; |
| 114 | + let default_db_env = read_env(PSQL_DB_VAR)?; |
| 115 | + let vss_db_env = read_env(PSQL_VSS_DB_VAR)?; |
| 116 | + let tls_config_env = read_env(PSQL_TLS_VAR)?; |
| 117 | + let crt_pem_env = read_env(PSQL_CERT_PEM_VAR)?; |
| 118 | + |
| 119 | + let ( |
| 120 | + username_config, |
| 121 | + password_config, |
| 122 | + address_config, |
| 123 | + default_db_config, |
| 124 | + vss_db_config, |
| 125 | + tls_config, |
| 126 | + ) = match postgresql_config { |
| 127 | + Some(c) => ( |
| 128 | + c.username, |
| 129 | + c.password, |
| 130 | + c.address, |
| 131 | + c.default_database, |
| 132 | + c.vss_database, |
| 133 | + c.tls.map(|tls| tls.crt_pem), |
| 134 | + ), |
| 135 | + None => (None, None, None, None, None, None), |
| 136 | + }; |
| 137 | + |
| 138 | + let username = |
| 139 | + read_config(username_env, username_config, "PostgreSQL database username", PSQL_USER_VAR)?; |
| 140 | + let password = |
| 141 | + read_config(password_env, password_config, "PostgreSQL database password", PSQL_PASS_VAR)?; |
| 142 | + let address = |
| 143 | + read_config(address_env, address_config, "PostgreSQL service address", PSQL_ADDR_VAR)?; |
| 144 | + let default_db = read_config( |
| 145 | + default_db_env, |
| 146 | + default_db_config, |
| 147 | + "PostgreSQL default database name", |
| 148 | + PSQL_DB_VAR, |
| 149 | + )?; |
| 150 | + let vss_db = |
| 151 | + read_config(vss_db_env, vss_db_config, "PostgreSQL vss database name", PSQL_VSS_DB_VAR)?; |
| 152 | + |
| 153 | + let tls_config = |
| 154 | + crt_pem_env.map(|pem| Some(pem)).or(tls_config_env.map(|_| None)).or(tls_config); |
| 155 | + |
| 156 | + let postgresql_prefix = format!("postgresql://{}:{}@{}", username, password, address); |
| 157 | + |
| 158 | + Ok(Configuration { bind_address, rsa_pem, postgresql_prefix, default_db, vss_db, tls_config }) |
58 | 159 | } |
0 commit comments