this repo has no description
1use secrecy::{ExposeSecret, SecretString};
2use serde::Deserialize;
3use serde_aux::field_attributes::deserialize_number_from_string;
4use sqlx::postgres::{PgConnectOptions, PgSslMode};
5use std::convert::{TryFrom, TryInto};
6
7#[derive(Deserialize, Clone, Debug)]
8pub struct Settings {
9 pub application: ApplicationSettings,
10 pub database: DatabaseSettings,
11 pub hosts: HostnameSettings,
12 pub redis_uri: SecretString,
13}
14
15#[derive(Deserialize, Clone, Debug)]
16pub struct ApplicationSettings {
17 pub base_url: String,
18 pub hmac_secret: SecretString,
19 pub host: String,
20 #[serde(deserialize_with = "deserialize_number_from_string")]
21 pub port: u16,
22 pub session_key: String,
23}
24
25#[derive(Deserialize, Clone, Debug)]
26pub struct DatabaseSettings {
27 pub database_name: String,
28 pub host: String,
29 pub password: SecretString,
30 #[serde(deserialize_with = "deserialize_number_from_string")]
31 pub port: u16,
32 pub require_ssl: bool,
33 pub username: String,
34}
35
36impl DatabaseSettings {
37 pub fn connect_options(&self) -> PgConnectOptions {
38 let ssl_mode = if self.require_ssl {
39 PgSslMode::Require
40 } else {
41 PgSslMode::Prefer
42 };
43 PgConnectOptions::new()
44 .host(&self.host)
45 .username(&self.username)
46 .password(self.password.expose_secret())
47 .port(self.port)
48 .ssl_mode(ssl_mode)
49 .database(&self.database_name)
50 }
51}
52
53#[derive(Deserialize, Clone, Debug)]
54pub struct HostnameSettings {
55 pub client: String,
56}
57
58pub fn get_configuration() -> Result<Settings, config::ConfigError> {
59 let base_path = std::env::current_dir().expect("Failed to determine the current directory");
60 let configuration_directory = base_path.join("configuration");
61 let environment: Environment = std::env::var("APP_ENVIRONMENT")
62 .unwrap_or_else(|_| "local".into())
63 .try_into()
64 .expect("Failed to parse APP_ENVIRONMENT.");
65 let _ = dotenvy::from_filename(environment.dotenv()).unwrap_or_default();
66 let environment_filename = format!("{}.yaml", environment.as_str());
67 let settings = config::Config::builder()
68 .add_source(config::File::from(
69 configuration_directory.join("base.yaml"),
70 ))
71 .add_source(config::File::from(
72 configuration_directory.join(environment_filename),
73 ))
74 .add_source(
75 config::Environment::with_prefix("APP")
76 .prefix_separator("_")
77 .separator("__"),
78 )
79 .build()?;
80
81 settings.try_deserialize::<Settings>()
82}
83
84pub enum Environment {
85 Local,
86 Production,
87 Test,
88}
89
90impl Environment {
91 pub fn as_str(&self) -> &'static str {
92 match self {
93 Environment::Local => "local",
94 Environment::Production => "production",
95 Environment::Test => "test",
96 }
97 }
98
99 pub fn dotenv(&self) -> &'static str {
100 match self {
101 Environment::Local => ".env",
102 Environment::Production => ".env",
103 Environment::Test => ".env.test",
104 }
105 }
106}
107
108impl TryFrom<String> for Environment {
109 type Error = String;
110
111 fn try_from(s: String) -> Result<Self, Self::Error> {
112 match s.to_lowercase().as_str() {
113 "local" => Ok(Self::Local),
114 "production" => Ok(Self::Production),
115 "test" => Ok(Self::Test),
116 other => Err(format!(
117 "{} is not a supported environment. Use either `local`, `test` or `production`.",
118 other
119 )),
120 }
121 }
122}