♻️ Trabajando en la gestión de la configuración
This commit is contained in:
parent
a11a24ceee
commit
183b9ff6a1
1 changed files with 260 additions and 85 deletions
|
|
@ -45,7 +45,7 @@
|
|||
//!
|
||||
//! Sólo tienes que añadir el método [`settings()`](crate::core::module::ModuleTrait::settings) al
|
||||
//! implementar [`ModuleTrait`](crate::core::module::ModuleTrait) para tu módulo, devolviendo los
|
||||
//! nuevos valores predefinidos con la macro [`predefined_settings!`].
|
||||
//! nuevos valores predefinidos con la macro [`predefined_settings!`](crate::predefined_settings).
|
||||
//!
|
||||
//! Cuando se carga la configuración de la aplicación, estos valores podrán ser sobrescritos con los
|
||||
//! ajustes personalizados del entorno. Y sólo será realmente necesario incluir en los archivos de
|
||||
|
|
@ -86,129 +86,304 @@
|
|||
//! // Obtiene el valor (del tipo especificado) de una clave.
|
||||
//! let db_port: u16 = config::get_value::<u16>("my_module.database.db_port");
|
||||
//! ```
|
||||
//!
|
||||
//!
|
||||
//!
|
||||
//!
|
||||
//!
|
||||
//!
|
||||
//!
|
||||
//!
|
||||
//!
|
||||
//!
|
||||
//! # Loading specific type-safe settings
|
||||
//!
|
||||
//! You can use the TOML syntax to create new sections in your configuration
|
||||
//! files, just as *\[app\]*, *\[webserver\]* or *\[database\]* exist in global
|
||||
//! settings. Or also add new settings in existing sections.
|
||||
//!
|
||||
//! Then you just have to load the configuration to use it from your module or
|
||||
//! application.
|
||||
//!
|
||||
//! To do this, add [*serde*](https://docs.rs/serde) in your application's
|
||||
//! *Cargo.toml*:
|
||||
//!
|
||||
//! ```
|
||||
//! [dependencies]
|
||||
//! serde = { version = "1.0", features = ["derive"] }
|
||||
//! ```
|
||||
//!
|
||||
//! and use the [`config_map!`] macro to create a new static as follows:
|
||||
//!
|
||||
//! ```
|
||||
//! use pagetop::config_map;
|
||||
//! use serde::Deserialize;
|
||||
//!
|
||||
//! #[derive(Debug, Deserialize)]
|
||||
//! pub struct Section1 {
|
||||
//! pub var1: String,
|
||||
//! pub var2: u16,
|
||||
//! }
|
||||
//!
|
||||
//! #[derive(Debug, Deserialize)]
|
||||
//! pub struct MySettings {
|
||||
//! pub section1: Section1,
|
||||
//! }
|
||||
//!
|
||||
//! config_map!("Application settings.", MYSETTINGS, MySettings);
|
||||
//! ```
|
||||
//!
|
||||
//! Use the first argument of [`config_map!`] for documentation purposes.
|
||||
//!
|
||||
//! If `MYSETTINGS` contains variables that are not defined in the configuration
|
||||
//! files, the application will *panic!*. To avoid this, you can initialize the
|
||||
//! key=value settings with default values:
|
||||
//!
|
||||
//! ```
|
||||
//! config_map!(r#"
|
||||
//! My configuration settings for *section1* section.
|
||||
//! "#,
|
||||
//! MYSETTINGS,
|
||||
//! MySettings,
|
||||
//! "section1.var1" => "seven",
|
||||
//! "section1.var2" => 7
|
||||
//! );
|
||||
//! ```
|
||||
//!
|
||||
//! # How to access configuration
|
||||
//!
|
||||
//! * **Using the** [`config_get!`] **macro**
|
||||
//!
|
||||
//! It will return the value assigned for a given key or an empty String ("")
|
||||
//! if it doesn't exist:
|
||||
//!
|
||||
//! ```
|
||||
//! use pagetop::config_get;
|
||||
//!
|
||||
//! fn demo() {
|
||||
//! println!("Address: {}", config_get!("webserver.bind_address"));
|
||||
//! println!("Port: {}", config_get!("webserver.bind_port"));
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! * Or **using the static** [`SETTINGS`] **to get type-safe global settings**
|
||||
//!
|
||||
//! ```
|
||||
//! use pagetop::config::SETTINGS;
|
||||
//!
|
||||
//! fn demo() {
|
||||
//! println!("App name: {}", &SETTINGS.app.name);
|
||||
//! println!("App description: {}", &SETTINGS.app.description);
|
||||
//! println!("Value of PAGETOP_RUN_MODE: {}", &SETTINGS.app.run_mode);
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! * Or **using statics to get specific type-safe settings**
|
||||
//!
|
||||
//! Use this for your module or application specific configuration settings.
|
||||
//!
|
||||
//! ```
|
||||
//! fn demo() {
|
||||
//! println!("{}", &MYSETTINGS.section1.var1);
|
||||
//! println!("{}", &MYSETTINGS.section1.var2);
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
mod data;
|
||||
mod de;
|
||||
mod error;
|
||||
mod file;
|
||||
mod path;
|
||||
mod source;
|
||||
mod value;
|
||||
|
||||
use crate::{trace, LazyStatic};
|
||||
|
||||
use config_rs::{Config, ConfigError, File};
|
||||
use crate::config::data::ConfigData;
|
||||
use crate::config::file::File;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::default::Default;
|
||||
use std::env;
|
||||
use std::fmt::Debug;
|
||||
use std::str::FromStr;
|
||||
use std::sync::RwLock;
|
||||
|
||||
pub type PredefinedSettings = HashMap<&'static str, &'static str>;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! predefined_settings {
|
||||
( $($key:literal => $value:literal),* ) => {{
|
||||
#[allow(unused_mut)]
|
||||
let mut a = std::collections::HashMap::new();
|
||||
$(
|
||||
a.insert($key, $value);
|
||||
)*
|
||||
a
|
||||
}};
|
||||
}
|
||||
|
||||
/// Directorio donde se encuentran los archivos de configuración.
|
||||
const CONFIG_DIR: &str = "config";
|
||||
|
||||
/// Carga los valores originales "clave = valor" de los archivos de configuración. Con
|
||||
/// [`config_map`] se asignarán los ajustes globales ([`SETTINGS`]); y se podrán asignar los ajustes
|
||||
/// específicos de la aplicación, o también de un tema, módulo o componente.
|
||||
static CONFIG: LazyStatic<Config> = LazyStatic::new(|| {
|
||||
/// Original key=value settings loaded on application startup.
|
||||
/// Asigna los ajustes específicos de la aplicación, o de un tema, módulo o componente, en una
|
||||
/// estructura similar a [`SETTINGS`] con tipos de variables seguros. Produce un *panic!* en caso de
|
||||
/// asignaciones no válidas.
|
||||
pub static CONFIG: LazyStatic<ConfigData> = LazyStatic::new(|| {
|
||||
// Modo de ejecución según la variable de entorno PAGETOP_RUN_MODE. Por defecto 'default'.
|
||||
let run_mode = env::var("PAGETOP_RUN_MODE").unwrap_or_else(|_| "default".into());
|
||||
|
||||
// Inicializa los ajustes.
|
||||
let settings = Config::builder();
|
||||
let mut settings = ConfigData::default();
|
||||
|
||||
// Combina los archivos de configuración y asigna el modo de ejecución.
|
||||
settings
|
||||
// Primero añade configuración común a todos los entornos. Opcional.
|
||||
.add_source(File::with_name(&format!("{}/{}.toml", CONFIG_DIR, "common")).required(false))
|
||||
.merge(File::with_name(&format!("{}/{}.toml", CONFIG_DIR, "common")).required(false))
|
||||
.unwrap()
|
||||
|
||||
// Combina la configuración específica del entorno. Por defecto 'default.toml'. Opcional.
|
||||
.add_source(File::with_name(&format!("{}/{}.toml", CONFIG_DIR, run_mode)).required(false))
|
||||
.merge(File::with_name(&format!("{}/{}.toml", CONFIG_DIR, run_mode)).required(false))
|
||||
.unwrap()
|
||||
|
||||
// Combina la configuración local. Este archivo no debería incluirse en git. Opcional.
|
||||
.add_source(File::with_name(&format!("{}/{}.toml", CONFIG_DIR, "local")).required(false))
|
||||
.merge(File::with_name(&format!("{}/{}.toml", CONFIG_DIR, "local")).required(false))
|
||||
.unwrap()
|
||||
|
||||
// Salvaguarda el modo de ejecución.
|
||||
.set_default("app.run_mode", run_mode)
|
||||
.set("app.run_mode", run_mode)
|
||||
.unwrap();
|
||||
|
||||
.unwrap()
|
||||
.build()
|
||||
.unwrap()
|
||||
settings
|
||||
});
|
||||
|
||||
static DEFAULTS: LazyStatic<RwLock<PredefinedSettings>> = LazyStatic::new(||
|
||||
RwLock::new(predefined_settings![
|
||||
// [app]
|
||||
"app.name" => "PageTop Application",
|
||||
"app.description" => "Developed with the amazing PageTop framework.",
|
||||
"app.theme" => "Bootsier",
|
||||
"app.language" => "en-US",
|
||||
"app.direction" => "ltr",
|
||||
"app.startup_banner" => "Slant",
|
||||
#[macro_export]
|
||||
/// Loads specific type-safe settings for your module or application in a structure similar to
|
||||
/// [`SETTINGS`].
|
||||
///
|
||||
/// See [`How to load specific type-safe settings`](config/index.html#loading-specific-type-safe-settings).
|
||||
macro_rules! pub_config_map {
|
||||
(
|
||||
$doc:expr,
|
||||
$SETTINGS:ident,
|
||||
$Type:tt
|
||||
$(, $key:expr => $value:expr)*
|
||||
) => {
|
||||
$crate::doc_comment! {
|
||||
concat!($doc),
|
||||
|
||||
// [log]
|
||||
"log.tracing" => "Info",
|
||||
"log.rolling" => "Stdout",
|
||||
"log.path" => "log",
|
||||
"log.prefix" => "tracing.log",
|
||||
"log.format" => "Full",
|
||||
|
||||
// [database]
|
||||
"database.db_type" => "",
|
||||
"database.db_name" => "",
|
||||
"database.db_user" => "",
|
||||
"database.db_pass" => "",
|
||||
"database.db_host" => "localhost",
|
||||
"database.db_port" => "0",
|
||||
"database.max_pool_size" => "5",
|
||||
|
||||
// [webserver]
|
||||
"webserver.bind_address" => "localhost",
|
||||
"webserver.bind_port" => "8088",
|
||||
|
||||
// [dev]
|
||||
"dev.static_files" => ""
|
||||
])
|
||||
);
|
||||
|
||||
/// Una aplicación o módulo podrá añadir nuevos valores predefinidos de configuración.
|
||||
pub(crate) fn add_predefined_settings(defaults: PredefinedSettings) {
|
||||
DEFAULTS.write().unwrap().extend(defaults);
|
||||
pub static $SETTINGS: $crate::LazyStatic<$Type> = $crate::LazyStatic::new(|| {
|
||||
let mut settings = $crate::config::CONFIG.clone();
|
||||
$(
|
||||
settings.set_default($key, $value).unwrap();
|
||||
)*
|
||||
match settings.try_into() {
|
||||
Ok(c) => c,
|
||||
Err(e) => panic!("Error parsing settings: {}", e),
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Devuelve el valor (String) de una clave.
|
||||
pub fn get(key: &str) -> String {
|
||||
match CONFIG.get_string(key) {
|
||||
Ok(value) => value,
|
||||
Err(ConfigError::NotFound(_)) => match DEFAULTS.read().unwrap().get(key) {
|
||||
Some(value) => String::from(*value),
|
||||
/// Devuelve el valor (del tipo especificado) de una clave.
|
||||
///
|
||||
/// See [`How to access configuration`](config/index.html#how-to-access-configuration).
|
||||
pub fn get<T: FromStr + Default>(key: &str) -> T where <T as FromStr>::Err: Debug {
|
||||
match CONFIG.get_str(key) {
|
||||
Ok(value) => match value.parse::<T>() {
|
||||
Ok(value) => value,
|
||||
_ => {
|
||||
trace::debug!("Config value not found for key \"{}\"! Return empty string", key);
|
||||
trace::warn!("Failed to parse value for key \"{}\"! Return default empty value", key);
|
||||
Default::default()
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
trace::warn!("Can't read config value for key \"{}\"! Return empty string", key);
|
||||
trace::warn!("Can't get config value for key \"{}\"! Return default empty value", key);
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Devuelve el valor (del tipo especificado) de una clave.
|
||||
pub fn get_value<T: FromStr + Default>(key: &str) -> T where <T as FromStr>::Err: Debug {
|
||||
match get(key).parse::<T>() {
|
||||
Ok(value) => value,
|
||||
_ => {
|
||||
trace::warn!("Failed to parse value for key \"{}\"! Return default empty value", key);
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
#[rustfmt::skip]
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct App {
|
||||
pub name : String,
|
||||
pub description : String,
|
||||
pub theme : String,
|
||||
pub language : String,
|
||||
pub direction : String,
|
||||
pub startup_banner: String,
|
||||
pub run_mode : String,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Log {
|
||||
pub tracing : String,
|
||||
pub rolling : String,
|
||||
pub path : String,
|
||||
pub prefix : String,
|
||||
pub format : String,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Database {
|
||||
pub db_type : String,
|
||||
pub db_name : String,
|
||||
pub db_user : String,
|
||||
pub db_pass : String,
|
||||
pub db_host : String,
|
||||
pub db_port : u16,
|
||||
pub max_pool_size : u32,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Webserver {
|
||||
pub bind_address : String,
|
||||
pub bind_port : u16,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Dev {
|
||||
pub static_files : String,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Settings {
|
||||
pub app : App,
|
||||
pub log : Log,
|
||||
pub database : Database,
|
||||
pub webserver : Webserver,
|
||||
pub dev : Dev,
|
||||
}
|
||||
|
||||
pub_config_map!(r#"
|
||||
Ajustes globales con tipos seguros y valores predefinidos para las secciones *\[app\]*, *\[log\]*,
|
||||
*\[database\]*, *\[webserver\]* y *\[dev\]* de PageTop.
|
||||
|
||||
See [`How to access configuration`](index.html#how-to-access-configuration).
|
||||
"#,
|
||||
SETTINGS, Settings,
|
||||
|
||||
// [app]
|
||||
"app.name" => "PageTop Application",
|
||||
"app.description" => "Developed with the amazing PageTop framework.",
|
||||
"app.theme" => "Bootsier",
|
||||
"app.language" => "en-US",
|
||||
"app.direction" => "ltr",
|
||||
"app.startup_banner" => "Slant",
|
||||
|
||||
// [log]
|
||||
"log.tracing" => "Info",
|
||||
"log.rolling" => "Stdout",
|
||||
"log.path" => "log",
|
||||
"log.prefix" => "tracing.log",
|
||||
"log.format" => "Full",
|
||||
|
||||
// [database]
|
||||
"database.db_type" => "",
|
||||
"database.db_name" => "",
|
||||
"database.db_user" => "",
|
||||
"database.db_pass" => "",
|
||||
"database.db_host" => "localhost",
|
||||
"database.db_port" => 0,
|
||||
"database.max_pool_size" => 5,
|
||||
|
||||
// [webserver]
|
||||
"webserver.bind_address" => "localhost",
|
||||
"webserver.bind_port" => 8088,
|
||||
|
||||
// [dev]
|
||||
"dev.static_files" => ""
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue