diff --git a/src/api/cli/roadster/mod.rs b/src/api/cli/roadster/mod.rs index 42472589..74f5e5d3 100644 --- a/src/api/cli/roadster/mod.rs +++ b/src/api/cli/roadster/mod.rs @@ -11,6 +11,7 @@ use crate::app::App; use crate::config::environment::Environment; use crate::error::RoadsterResult; use async_trait::async_trait; +use std::path::PathBuf; use axum::extract::FromRef; use clap::{Parser, Subcommand}; @@ -60,6 +61,11 @@ pub struct RoadsterCli { #[clap(long, action)] pub allow_dangerous: bool, + /// The location of the config directory (where the app's config files are located). If + /// not provided, will default to `./config/`. + #[clap(long, value_name = "CONFIG_DIRECTORY", value_hint = clap::ValueHint::DirPath)] + pub config_dir: Option, + #[command(subcommand)] pub command: Option, } diff --git a/src/app/mod.rs b/src/app/mod.rs index 458ddf34..baadabff 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -89,7 +89,12 @@ where #[cfg(not(feature = "cli"))] let environment: Option = None; - let config = AppConfig::new(environment)?; + #[cfg(feature = "cli")] + let config_dir = roadster_cli.config_dir.as_ref().cloned(); + #[cfg(not(feature = "cli"))] + let config_dir: Option = None; + + let config = AppConfig::new_with_config_dir(environment, config_dir)?; app.init_tracing(&config)?; diff --git a/src/config/app_config.rs b/src/config/app_config.rs index 11318041..7b4e3b99 100644 --- a/src/config/app_config.rs +++ b/src/config/app_config.rs @@ -15,7 +15,7 @@ use serde_derive::{Deserialize, Serialize}; use serde_json::Value; use std::collections::BTreeMap; use std::fs; -use std::path::Path; +use std::path::{Path, PathBuf}; use tracing::warn; use validator::Validate; @@ -67,10 +67,21 @@ pub const ENV_VAR_PREFIX: &str = "ROADSTER"; pub const ENV_VAR_SEPARATOR: &str = "__"; impl AppConfig { + #[deprecated( + since = "0.6.2", + note = "This wasn't intended to be made public and may be removed in a future version." + )] + pub fn new(environment: Option) -> RoadsterResult { + Self::new_with_config_dir(environment, Some(PathBuf::from("config/"))) + } + // This runs before tracing is initialized, so we need to use `println` in order to // log from this method. #[allow(clippy::disallowed_macros)] - pub fn new(environment: Option) -> RoadsterResult { + pub(crate) fn new_with_config_dir( + environment: Option, + config_dir: Option, + ) -> RoadsterResult { dotenv().ok(); let environment = if let Some(environment) = environment { @@ -81,11 +92,17 @@ impl AppConfig { }; let environment_str: &str = environment.clone().into(); + let config_root_dir = config_dir + .unwrap_or_else(|| PathBuf::from("config/")) + .canonicalize()?; + + println!("Loading configuration from directory {config_root_dir:?}"); + let config = Self::default_config(environment); - let config = config_env_file("default", config); - let config = config_env_dir("default", config)?; - let config = config_env_file(environment_str, config); - let config = config_env_dir(environment_str, config)?; + let config = config_env_file("default", &config_root_dir, config); + let config = config_env_dir("default", &config_root_dir, config)?; + let config = config_env_file(environment_str, &config_root_dir, config); + let config = config_env_dir(environment_str, &config_root_dir, config)?; let config = config .add_source( config::Environment::default() @@ -197,12 +214,11 @@ impl AppConfig { /// [`ConfigBuilder`]. If no such file exists, does nothing. fn config_env_file( environment: &str, + config_dir: &Path, config: ConfigBuilder, ) -> ConfigBuilder { // Todo: allow other file formats? - let filename = format!("config/{environment}.toml"); - - let path = Path::new(&filename); + let path = config_dir.join(format!("{environment}.toml")); if !path.is_file() { return config; } @@ -214,16 +230,15 @@ fn config_env_file( /// [`ConfigBuilder`]. If no such directory exists, does nothing. fn config_env_dir( environment: &str, + config_dir: &Path, config: ConfigBuilder, ) -> RoadsterResult> { - let dirname = format!("config/{environment}"); - - let path = Path::new(&dirname); + let path = config_dir.join(environment); if !path.is_dir() { return Ok(config); } - config_env_dir_recursive(path, config) + config_env_dir_recursive(&path, config) } /// Helper method for [`config_env_dir`] to recursively add config files in the given path