Skip to content

Commit

Permalink
refactor config module
Browse files Browse the repository at this point in the history
  • Loading branch information
robatipoor committed Feb 7, 2025
1 parent 3e40925 commit 93dbcd7
Show file tree
Hide file tree
Showing 15 changed files with 101 additions and 97 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ settings
```
#### Configure with environment variables
```bash
export APP__SERVER__PORT=8080
export APP__SERVER__ADDR=127.0.0.1
export PROD_APP__SERVER__PORT=8080
export PROD_APP__SERVER__ADDR=127.0.0.1
```
#### Switching profiles
Before running the application, export this variable:
Expand Down
41 changes: 20 additions & 21 deletions docker/dev/.env
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
APP_PROFILE=dev
RUST_BACKTRACE=1
RUST_LOG=info
DATABASE_URL=postgres://username:password@postgres-db:5432/database_name
APP__SERVER__PORT=8080
APP__SERVER__ADDR=0.0.0.0
APP__DB__USERNAME=username
APP__DB__PASSWORD=password
APP__DB__PORT=5432
APP__DB__HOST=postgres-db
APP__DB__DATABASE_NAME=database_name
APP__REDIS__USERNAME=redis_user
APP__REDIS__PASSWORD=
APP__REDIS__PORT=6379
APP__REDIS__HOST=redis-db
APP__REDIS__DATABASE_NAME=
APP__EMAIL__USERNAME=email_user
APP__EMAIL__PASSWORD=email_pass
APP__EMAIL__PORT=1025
APP__EMAIL__HOST=mailhog
APP__SECRET__PRIVATE_ACCESS_KEY=static/key/private_access_rsa_key.pem
APP__SECRET__PUBLIC_ACCESS_KEY=static/key/public_access_rsa_key.pem
APP__SECRET__PRIVATE_REFRESH_KEY=static/key/private_refresh_rsa_key.pem
APP__SECRET__PUBLIC_REFRESH_KEY=static/key/public_refresh_rsa_key.pem
DEV_APP__SERVER__PORT=8080
DEV_APP__SERVER__ADDR=0.0.0.0
DEV_APP__DB__USERNAME=username
DEV_APP__DB__PASSWORD=password
DEV_APP__DB__PORT=5432
DEV_APP__DB__HOST=postgres-db
DEV_APP__DB__DATABASE_NAME=database_name
DEV_APPP__REDIS__USERNAME=redis_user
DEV_APP__REDIS__PASSWORD=
DEV_APP__REDIS__PORT=6379
DEV_APP__REDIS__HOST=redis-db
DEV_APP__REDIS__DATABASE_NAME=
DEV_APP__EMAIL__USERNAME=email_user
DEV_APP__EMAIL__PASSWORD=email_pass
DEV_APP__EMAIL__PORT=1025
DEV_APP__EMAIL__HOST=mailhog
DEV_APP__SECRET__PRIVATE_ACCESS_KEY=static/key/private_access_rsa_key.pem
DEV_APP__SECRET__PUBLIC_ACCESS_KEY=static/key/public_access_rsa_key.pem
DEV_APP__SECRET__PRIVATE_REFRESH_KEY=static/key/private_refresh_rsa_key.pem
DEV_APP__SECRET__PUBLIC_REFRESH_KEY=static/key/public_refresh_rsa_key.pem
22 changes: 11 additions & 11 deletions docker/dev/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ services:
image: "ghcr.io/robatipoor/rustfulapi:latest"
container_name: application
ports:
- "${APP__SERVER__PORT}:${APP__SERVER__PORT}"
- "${DEV_APP__SERVER__PORT}:${DEV_APP__SERVER__PORT}"
networks:
- server-side
depends_on:
Expand All @@ -19,31 +19,31 @@ services:
networks:
- server-side
environment:
- POSTGRES_USER=${APP__DB__USERNAME}
- POSTGRES_PASSWORD=${APP__DB__PASSWORD}
- POSTGRES_DB=${APP__DB__DATABASE_NAME}
- POSTGRES_HOST=${APP__DB__HOST}
- POSTGRES_USER=${DEV_APP__DB__USERNAME}
- POSTGRES_PASSWORD=${DEV_APP__DB__PASSWORD}
- POSTGRES_DB=${DEV_APP__DB__DATABASE_NAME}
- POSTGRES_HOST=${DEV_APP__DB__HOST}
ports:
- "${APP__DB__PORT}:${APP__DB__PORT}"
- "${DEV_APP__DB__PORT}:${DEV_APP__DB__PORT}"
redis-db:
image: "redis:latest"
container_name: redis-database
restart: always
networks:
- server-side
environment:
- REDIS_USER=${APP__REDIS__USERNAME}
- REDIS_PASSWORD=${APP__REDIS__PASSWORD}
- REDIS_DB=${APP__REDIS__DATABASE_NAME}
- REDIS_USER=${DEV_APP__REDIS__USERNAME}
- REDIS_PASSWORD=${DEV_APP__REDIS__PASSWORD}
- REDIS_DB=${DEV_APP__REDIS__DATABASE_NAME}
ports:
- "${APP__REDIS__PORT}:${APP__REDIS__PORT}"
- "${DEV_APP__REDIS__PORT}:${DEV_APP__REDIS__PORT}"
mailhog:
image: "mailhog/mailhog"
networks:
- server-side
restart: always
ports:
- "${APP__EMAIL__PORT}:${APP__EMAIL__PORT}"
- "${DEV_APP__EMAIL__PORT}:${DEV_APP__EMAIL__PORT}"
- "8025:8025"
pgadmin:
image: dpage/pgadmin4
Expand Down
41 changes: 20 additions & 21 deletions docker/prod/.env
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
APP_PROFILE=prod
RUST_BACKTRACE=1
RUST_LOG=info
DATABASE_URL=postgres://username:password@localhost:5432/database_name
APP__SERVER__PORT=8080
APP__SERVER__ADDR=0.0.0.0
APP__DB__USERNAME=username
APP__DB__PASSWORD=password
APP__DB__PORT=5432
APP__DB__HOST=postgres-db
APP__DB__DATABASE_NAME=database_name
APP__REDIS__USERNAME=redis_user
APP__REDIS__PASSWORD=
APP__REDIS__PORT=6379
APP__REDIS__HOST=redis-db
APP__REDIS__DATABASE_NAME=
APP__EMAIL__USERNAME=username
APP__EMAIL__PASSWORD=passowrd
APP__EMAIL__PORT=587
APP__EMAIL__HOST=mail.rustfulapi.com
APP__SECRET__PRIVATE_ACCESS_KEY=static/key/private_access_rsa_key.pem
APP__SECRET__PUBLIC_ACCESS_KEY=static/key/public_access_rsa_key.pem
APP__SECRET__PRIVATE_REFRESH_KEY=static/key/private_refresh_rsa_key.pem
APP__SECRET__PUBLIC_REFRESH_KEY=static/key/public_refresh_rsa_key.pem
PROD_APP__SERVER__PORT=8080
PROD_APP__SERVER__ADDR=0.0.0.0
PROD_APP__DB__USERNAME=username
PROD_APP__DB__PASSWORD=password
PROD_APP__DB__PORT=5432
PROD_APP__DB__HOST=postgres-db
PROD_APP__DB__DATABASE_NAME=database_name
PROD_APP__REDIS__USERNAME=redis_user
PROD_APP__REDIS__PASSWORD=
PROD_APP__REDIS__PORT=6379
PROD_APP__REDIS__HOST=redis-db
PROD_APP__REDIS__DATABASE_NAME=
PROD_APP__EMAIL__USERNAME=username
PROD_APP__EMAIL__PASSWORD=passowrd
PROD_APP__EMAIL__PORT=587
PROD_APP__EMAIL__HOST=mail.rustfulapi.com
PROD_APP__SECRET__PRIVATE_ACCESS_KEY=static/key/private_access_rsa_key.pem
PROD_APP__SECRET__PUBLIC_ACCESS_KEY=static/key/public_access_rsa_key.pem
PROD_APP__SECRET__PRIVATE_REFRESH_KEY=static/key/private_refresh_rsa_key.pem
PROD_APP__SECRET__PUBLIC_REFRESH_KEY=static/key/public_refresh_rsa_key.pem
18 changes: 9 additions & 9 deletions docker/prod/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ services:
networks:
- server-side
environment:
- POSTGRES_USER=${APP__DB__USERNAME}
- POSTGRES_PASSWORD=${APP__DB__PASSWORD}
- POSTGRES_DB=${APP__DB__DATABASE_NAME}
- POSTGRES_HOST=${APP__DB__HOST}
- POSTGRES_USER=${PROD_APP__DB__USERNAME}
- POSTGRES_PASSWORD=${PROD_APP__DB__PASSWORD}
- POSTGRES_DB=${PROD_APP__DB__DATABASE_NAME}
- POSTGRES_HOST=${PROD_APP__DB__HOST}
ports:
- "${APP_DB__PORT}:${APP_DB__PORT}"
- "${PROD_APP_DB__PORT}:${PROD_APP_DB__PORT}"
volumes:
- pg_data:/var/lib/postgresql/data
redis-db:
Expand All @@ -33,11 +33,11 @@ services:
networks:
- server-side
environment:
- REDIS_USER=${APP__REDIS__USERNAME}
- REDIS_PASSWORD=${APP__REDIS__PASSWORD}
- REDIS_DB=${APP__REDIS__DATABASE_NAME}
- REDIS_USER=${PROD_APP__REDIS__USERNAME}
- REDIS_PASSWORD=${PROD_APP__REDIS__PASSWORD}
- REDIS_DB=${PROD_APP__REDIS__DATABASE_NAME}
ports:
- "${APP__REDIS__PORT}:${APP__REDIS__PORT}"
- "${PROD_APP__REDIS__PORT}:${PROD_APP__REDIS__PORT}"
pgadmin:
image: dpage/pgadmin4
container_name: pgadmin
Expand Down
2 changes: 0 additions & 2 deletions settings/dev.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
profile = "dev"

[server]
addr = "127.0.0.1"
port = 8_080
Expand Down
2 changes: 0 additions & 2 deletions settings/prod.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
profile = "prod"

[server]
addr = "0.0.0.0"
port = 8_080
Expand Down
2 changes: 0 additions & 2 deletions settings/test.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
profile = "test"

[server]
addr = "127.0.0.1"
port = 0
Expand Down
4 changes: 1 addition & 3 deletions src/client/http.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::time::Duration;

use reqwest::Response;
use serde::Serialize;

Expand Down Expand Up @@ -34,7 +32,7 @@ impl ClientBuilder for HttpClient {
fn build_from_config(config: &AppConfig) -> AppResult<Self> {
Ok(
reqwest::Client::builder()
.timeout(Duration::from_secs(config.http.timeout))
.timeout(config.http.timeout)
.build()?,
)
}
Expand Down
9 changes: 9 additions & 0 deletions src/configure/deserialize.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use serde::{Deserialize, Deserializer};
use std::time::Duration;

pub fn deserialize_duration<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
D: Deserializer<'de>,
{
Ok(Duration::from_secs(u64::deserialize(deserializer)?))
}
8 changes: 4 additions & 4 deletions src/configure/env.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
use std::str::FromStr;

use super::Profile;
use config::ConfigError;

pub fn get_env_source(prefix: &str) -> config::Environment {
config::Environment::with_prefix(prefix)
.prefix_separator("__")
.separator("__")
}

pub fn get_profile() -> Result<Profile, config::ConfigError> {
pub fn get_profile() -> Result<Profile, String> {
std::env::var("APP_PROFILE")
.map(|env| Profile::from_str(&env).map_err(|e| ConfigError::Message(e.to_string())))
.unwrap_or_else(|_e| Ok(Profile::Dev))
.map(|env| Profile::from_str(&env))
.map_err(|e| e.to_string())?
.map_err(|e| e.to_string())
}
6 changes: 5 additions & 1 deletion src/configure/http.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
use std::time::Duration;

use crate::configure::deserialize::deserialize_duration;
use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct HttpClientConfig {
pub timeout: u64,
#[serde(deserialize_with = "deserialize_duration")]
pub timeout: Duration,
}
29 changes: 16 additions & 13 deletions src/configure/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use std::str::FromStr;

use ::tracing::info;
use config::{ConfigError, Environment};
use env::get_env_source;
use serde::Deserialize;

use crate::util::dir::get_project_root;
Expand All @@ -12,6 +11,7 @@ use self::{
};

pub mod db;
pub mod deserialize;
pub mod email;
pub mod env;
pub mod http;
Expand All @@ -25,7 +25,6 @@ pub mod worker;

#[derive(Debug, Deserialize, Clone)]
pub struct AppConfig {
pub profile: Profile,
pub server: ServerConfig,
pub db: DatabaseConfig,
pub redis: RedisConfig,
Expand All @@ -37,16 +36,12 @@ pub struct AppConfig {
}

impl AppConfig {
pub fn read(env_src: Environment) -> Result<Self, config::ConfigError> {
pub fn read(profile: Profile) -> Result<Self, config::ConfigError> {
let config_dir = get_settings_dir()?;
let profile = std::env::var("APP_PROFILE")
.map(|env| Profile::from_str(&env).map_err(|e| ConfigError::Message(e.to_string())))
.unwrap_or_else(|_e| Ok(Profile::Dev))?;
let profile_filename = format!("{profile}.toml");
let config = config::Config::builder()
.add_source(config::File::from(config_dir.join("base.toml")))
.add_source(config::File::from(config_dir.join(profile_filename)))
.add_source(env_src)
.add_source(config::File::from(config_dir.join(profile.filename())))
.add_source(profile.env_source())
.build()?;
info!("Successfully read config profile: {profile}.");
config.try_deserialize()
Expand Down Expand Up @@ -84,15 +79,23 @@ pub enum Profile {
Prod,
}

impl Profile {
fn filename(&self) -> String {
format!("{self}.toml")
}

fn env_source(&self) -> Environment {
get_env_source(&format!("{}_APP", self.to_string().to_uppercase()))
}
}

#[cfg(test)]
mod tests {
use self::env::get_env_source;

pub use super::*;

#[test]
pub fn test_read_app_config() {
let _config = AppConfig::read(get_env_source("TEST_APP")).unwrap();
let _config = AppConfig::read(Profile::Test).unwrap();
}

#[test]
Expand Down
5 changes: 2 additions & 3 deletions src/constant/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ use utoipa::OpenApi;

use crate::{
client::{email::EmailClient, http::HttpClient, redis::RedisClient, ClientBuilder},
configure::{env::get_env_source, get_static_dir, template::TemplateEngine},
configure::{env::get_profile, get_static_dir, template::TemplateEngine},
handler::openapi::ApiDoc,
};

pub const ENV_PREFIX: &str = "APP";
pub const CODE_LEN: usize = 5;
pub const CLIENT_TIMEOUT: Duration = Duration::from_secs(120);
pub const EXPIRE_SESSION_CODE_SECS: Duration = Duration::from_secs(2000);
Expand All @@ -30,7 +29,7 @@ pub static IMAGES_PATH: LazyLock<PathBuf> =
pub static APP_IMAGE: LazyLock<PathBuf> =
LazyLock::new(|| get_static_dir().unwrap().join("images/logo.jpg"));
pub static CONFIG: LazyLock<crate::configure::AppConfig> =
LazyLock::new(|| crate::configure::AppConfig::read(get_env_source(ENV_PREFIX)).unwrap());
LazyLock::new(|| crate::configure::AppConfig::read(get_profile().unwrap()).unwrap());
pub static HTTP: LazyLock<reqwest::Client> =
LazyLock::new(|| HttpClient::build_from_config(&CONFIG).unwrap());
pub static REDIS: LazyLock<RedisClient> =
Expand Down
5 changes: 2 additions & 3 deletions tests/api/context/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ use std::sync::LazyLock;

use rustfulapi::{
client::database::{drop_database, migrate_database, setup_new_database, DatabaseClient},
configure::{env::get_env_source, AppConfig},
constant::ENV_PREFIX,
configure::{AppConfig, Profile},
error::AppResult,
server::{self, state::AppState, worker::MessengerTask},
};
Expand All @@ -27,7 +26,7 @@ pub struct AppTestContext {
impl AsyncTestContext for AppTestContext {
async fn setup() -> Self {
LazyLock::force(&INIT_SUBSCRIBER);
let mut config = AppConfig::read(get_env_source(ENV_PREFIX)).unwrap();
let mut config = AppConfig::read(Profile::Test).unwrap();
let default_db = setup_new_database(&mut config).await.unwrap();
let server = server::AppServer::new(config).await.unwrap();
migrate_database(&server.state.db).await.unwrap();
Expand Down

0 comments on commit 93dbcd7

Please sign in to comment.