configuration.rs 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. use serde_aux::field_attributes::deserialize_number_from_string;
  2. use sqlx::postgres::{PgConnectOptions, PgSslMode};
  3. use std::convert::{TryFrom, TryInto};
  4. #[derive(serde::Deserialize, Clone, Debug)]
  5. pub struct Settings {
  6. pub database: DatabaseSettings,
  7. pub application: ApplicationSettings,
  8. }
  9. // We are using 127.0.0.1 as our host in address, we are instructing our
  10. // application to only accept connections coming from the same machine. However,
  11. // request from the hose machine which is not seen as local by our Docker image.
  12. //
  13. // Using 0.0.0.0 as host to instruct our application to accept connections from
  14. // any network interface. So using 127.0.0.1 for our local development and set
  15. // it to 0.0.0.0 in our Docker images.
  16. //
  17. #[derive(serde::Deserialize, Clone, Debug)]
  18. pub struct ApplicationSettings {
  19. #[serde(deserialize_with = "deserialize_number_from_string")]
  20. pub port: u16,
  21. pub host: String,
  22. }
  23. #[derive(serde::Deserialize, Clone, Debug)]
  24. pub struct DatabaseSettings {
  25. pub username: String,
  26. pub password: String,
  27. #[serde(deserialize_with = "deserialize_number_from_string")]
  28. pub port: u16,
  29. pub host: String,
  30. pub database_name: String,
  31. pub require_ssl: bool,
  32. }
  33. impl DatabaseSettings {
  34. pub fn without_db(&self) -> PgConnectOptions {
  35. let ssl_mode = if self.require_ssl {
  36. PgSslMode::Require
  37. } else {
  38. PgSslMode::Prefer
  39. };
  40. PgConnectOptions::new()
  41. .host(&self.host)
  42. .username(&self.username)
  43. .password(&self.password)
  44. .port(self.port)
  45. .ssl_mode(ssl_mode)
  46. }
  47. pub fn with_db(&self) -> PgConnectOptions {
  48. self.without_db().database(&self.database_name)
  49. }
  50. }
  51. pub fn get_configuration() -> Result<Settings, config::ConfigError> {
  52. let mut settings = config::Config::default();
  53. let base_path = std::env::current_dir().expect("Failed to determine the current directory");
  54. let configuration_dir = base_path.join("configuration");
  55. settings.merge(config::File::from(configuration_dir.join("base")).required(true))?;
  56. let environment: Environment = std::env::var("APP_ENVIRONMENT")
  57. .unwrap_or_else(|_| "local".into())
  58. .try_into()
  59. .expect("Failed to parse APP_ENVIRONMENT.");
  60. settings.merge(config::File::from(configuration_dir.join(environment.as_str())).required(true))?;
  61. // Add in settings from environment variables (with a prefix of APP and '__' as
  62. // separator) E.g. `APP_APPLICATION__PORT=5001 would set
  63. // `Settings.application.port`
  64. settings.merge(config::Environment::with_prefix("app").separator("__"))?;
  65. settings.try_into()
  66. }
  67. /// The possible runtime environment for our application.
  68. pub enum Environment {
  69. Local,
  70. Production,
  71. }
  72. impl Environment {
  73. pub fn as_str(&self) -> &'static str {
  74. match self {
  75. Environment::Local => "local",
  76. Environment::Production => "production",
  77. }
  78. }
  79. }
  80. impl TryFrom<String> for Environment {
  81. type Error = String;
  82. fn try_from(s: String) -> Result<Self, Self::Error> {
  83. match s.to_lowercase().as_str() {
  84. "local" => Ok(Self::Local),
  85. "production" => Ok(Self::Production),
  86. other => Err(format!(
  87. "{} is not a supported environment. Use either `local` or `production`.",
  88. other
  89. )),
  90. }
  91. }
  92. }