/
configuration.rs
135 lines (119 loc) · 3.86 KB
/
configuration.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
use crate::SubscriberEmail;
use config::{Config, File};
use secrecy::{ExposeSecret, Secret};
use serde::Deserialize;
use serde_aux::prelude::deserialize_number_from_string;
#[derive(Deserialize, Clone, Debug)]
pub struct Settings {
pub database: DatabaseSettings,
pub application: ApplicationSettings,
pub email_client: EmailClientSettings,
pub jwt_handler: JwtHandlerSettings,
}
pub enum Environment {
Local,
Test,
Production,
}
#[derive(Deserialize, Clone, Debug)]
pub struct DatabaseSettings {
pub username: String,
pub password: Secret<String>,
#[serde(deserialize_with = "deserialize_number_from_string")]
pub port: u16,
pub host: String,
pub database_name: String,
}
#[derive(Deserialize, Clone, Debug)]
pub struct ApplicationSettings {
#[serde(deserialize_with = "deserialize_number_from_string")]
pub port: u16,
pub base_url: String,
pub logging_levels: Vec<String>,
pub cors_base_url: Vec<String>,
}
#[derive(Deserialize, Clone, Debug)]
pub struct EmailClientSettings {
pub base_url: String,
pub sender_email: String,
pub api_key: Secret<String>,
#[serde(deserialize_with = "deserialize_number_from_string")]
pub timeout_milliseconds: u64,
}
#[derive(Deserialize, Clone, Debug)]
pub struct JwtHandlerSettings {
pub private_key: Secret<String>,
pub public_key: String,
#[serde(deserialize_with = "deserialize_number_from_string")]
pub expiration_minutes: i64,
}
impl Environment {
pub fn as_str(&self) -> &'static str {
match self {
Environment::Local => "local",
Environment::Production => "production",
Environment::Test => "test",
}
}
}
impl TryFrom<String> for Environment {
type Error = String;
fn try_from(s: String) -> Result<Self, Self::Error> {
match s.to_lowercase().as_str() {
"local" => Ok(Self::Local),
"production" => Ok(Self::Production),
"test" => Ok(Self::Test),
other => Err(format!(
"{} is not a supported environment. Use either `local` or `production`.",
other
)),
}
}
}
pub fn get_configuration() -> Result<Settings, config::ConfigError> {
let base_path = std::env::current_dir().expect("Failed to determine the current directory");
let configuration_directory = base_path.join("configuration");
let environment: Environment = std::env::var("APP__ENVIRONMENT")
.unwrap_or_else(|_| "local".into())
.try_into()
.expect("Failed to parse APP_ENVIRONMENT.");
let settings = Config::builder()
.add_source(File::from(configuration_directory.join("base")).required(true))
.add_source(File::from(configuration_directory.join(environment.as_str())).required(true))
.add_source(
config::Environment::with_prefix("APP")
.try_parsing(true)
.separator("__"),
)
.build()?;
settings.try_deserialize::<Settings>()
}
impl DatabaseSettings {
pub fn connection_string(&self) -> Secret<String> {
Secret::new(format!(
"postgres://{}:{}@{}:{}/{}?sslmode=disable",
self.username,
self.password.expose_secret(),
self.host,
self.port,
self.database_name
))
}
pub fn connection_string_without_db(&self) -> Secret<String> {
Secret::new(format!(
"postgres://{}:{}@{}:{}?sslmode=disable",
self.username,
self.password.expose_secret(),
self.host,
self.port
))
}
}
impl EmailClientSettings {
pub fn sender(&self) -> Result<SubscriberEmail, String> {
SubscriberEmail::parse(self.sender_email.clone())
}
pub fn timeout(&self) -> std::time::Duration {
std::time::Duration::from_millis(self.timeout_milliseconds)
}
}