diff --git a/pagetop/Cargo.toml b/pagetop/Cargo.toml index 2273a760..45fc2c74 100644 --- a/pagetop/Cargo.toml +++ b/pagetop/Cargo.toml @@ -24,7 +24,6 @@ categories = [ [dependencies] async-trait = "0.1.57" concat-string = "1.0.1" -doc-comment = "0.3.3" figlet-rs = "0.1.4" futures = "0.3.24" once_cell = "1.15.0" diff --git a/pagetop/src/app/application.rs b/pagetop/src/app/application.rs index 4875a0ee..2512da8f 100644 --- a/pagetop/src/app/application.rs +++ b/pagetop/src/app/application.rs @@ -38,9 +38,6 @@ impl Application { // Registra acciones de los módulos. module::all::register_actions(); - // Inicializa valores predefinidos de configuración. - // module::all::init_settings(); - // Inicializa los módulos. module::all::init_modules(); diff --git a/pagetop/src/config.rs b/pagetop/src/config.rs index d0b1ec0c..a372b835 100644 --- a/pagetop/src/config.rs +++ b/pagetop/src/config.rs @@ -1,34 +1,36 @@ //! Gestión de la configuración. //! -//! Carga durante el arranque la configuración de la aplicación en forma de pares `clave = valor` -//! recogidos en archivos [TOML](https://toml.io). +//! Carga la configuración de la aplicación en forma de pares `clave = valor` recogidos en archivos +//! [TOML](https://toml.io). //! //! La metodología [The Twelve-Factor App](https://12factor.net/es/) define **la configuración de //! una aplicación como todo lo que puede variar entre despliegues**, diferenciando entre entornos //! de desarrollo, pre-producción, producción, etc. //! -//! A veces las aplicaciones guardan configuraciones como constantes en el código, lo que supone una -//! violación de esta metodología. Debe existir una **estricta separación entre la configuración y -//! el código**. La configuración variará sustancialmente en cada despliegue, el código no. +//! A veces las aplicaciones guardan configuraciones como constantes en el código, lo que implica +//! una violación de esta metodología. PageTop recomienda una **estricta separación entre código y +//! configuración**. La configuración variará en cada tipo de despliegue, y el código no. +//! //! //! # Cómo usar archivos de configuración //! -//! Si tu aplicación requiere archivos de configuración debes crear un directorio llamado *config* al -//! mismo nivel del archivo *Cargo.toml* de tu proyecto (o del ejecutable binario de la aplicación). +//! Si tu aplicación requiere archivos de configuración debes crear un directorio llamado *config* +//! al mismo nivel del archivo *Cargo.toml* de tu proyecto (o del ejecutable binario de la +//! aplicación). //! //! Guarda la configuración usando archivos TOML asumiendo el siguiente orden de lectura secuencial //! (todos los archivos son opcionales): //! -//! 1. *config/common.toml*, útil para los ajustes comunes para cualquier entorno. Estos valores +//! 1. **config/common.toml**, útil para los ajustes comunes para cualquier entorno. Estos valores //! podrán ser sobrescritos al fusionar los archivos de configuración siguientes. //! -//! 2. *config/{archivo}.toml*, donde *{archivo}* puede definirse mediante la variable de entorno +//! 2. **config/{archivo}.toml**, donde *{archivo}* puede definirse mediante la variable de entorno //! PAGETOP_RUN_MODE: //! -//! * Si no está definida, se asumirá *default* por defecto, y PageTop cargará el archivo de +//! * Si no lo está, se asumirá *default* por defecto, y PageTop cargará el archivo de //! configuración *config/default.toml* si existe. //! -//! * De esta manera, se pueden tener diferentes ajustes de configuración para diferentes +//! * De esta manera se pueden tener diferentes ajustes de configuración para diferentes //! entornos de ejecución. Por ejemplo, para *devel.toml*, *staging.toml* o *production.toml*. //! O también para *server1.toml* o *server2.toml*. Sólo uno será cargado. //! @@ -36,158 +38,90 @@ //! sensible asociada al entorno correspondiente. Estos archivos no deberían ser publicados en //! el repositorio Git por razones de seguridad. //! -//! 3. *config/local.toml*, para añadir o sobrescribir ajustes. +//! 3. **config/local.toml**, para añadir o sobrescribir ajustes previos. //! -//! # Cómo añadir valores predefinidos de configuración -//! -//! Si nuestra **aplicación** o **módulo** requiere sus propios ajustes de configuración, es -//! recomendable (aunque no imprescindible) inicializarlos antes de su uso. -//! -//! 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!`](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 -//! configuración los ajustes que difieran de los predefinidos. -//! -//! ``` -//! use pagetop::prelude::*; -//! -//! pub_const_handler!(MY_MODULE_HANDLER); -//! -//! pub struct MyModule; -//! -//! impl ModuleTrait for MyModule { -//! fn handler(&self) -> Handler { -//! MY_MODULE_HANDLER -//! } -//! -//! fn settings(&self) -> PredefinedSettings { -//! predefined_settings![ -//! // Valores predefinidos para "my_module". -//! "my_module.name" => "Name", -//! "my_module.desc" => "Description", -//! // Valores predefinidos para "my_module.database". -//! "my_module.database.db_port" => "3306" -//! ] -//! } -//! } -//! ``` -//! -//! # Cómo obtener los valores de configuración -//! -//! ``` -//! use pagetop::config; -//! -//! // Obtiene el valor (String) de una clave. -//! let name: String = config::get("my_module.name"); -//! -//! // Obtiene el valor (del tipo especificado) de una clave. -//! let db_port: u16 = config::get_value::("my_module.database.db_port"); -//! ``` +//! Los ajustes de configuración siempre son de sólo lectura. //! //! +//! # Cómo añadir ajustes de configuración //! +//! Puedes usar la sintaxis de TOML para crear nuevas secciones en los archivos de configuración, +//! del mismo modo que *\[app\]* o *\[webserver\]* existen en la configuración predeterminada (ver +//! [`SETTINGS`]). //! -//! -//! -//! -//! -//! -//! -//! # 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*: +//! Para cargar y usar esta nueva configuración desde tu **aplicación** o **módulo** tienes que +//! incluir [*serde*](https://docs.rs/serde) en las dependencias de tu archivo *Cargo.toml*: //! //! ``` //! [dependencies] //! serde = { version = "1.0", features = ["derive"] } //! ``` //! -//! and use the [`config_map!`] macro to create a new static as follows: +//! y añadir en tu código una declaración similar a la que utiliza [`SETTINGS`] para instanciar +//! ([`LazyStatic`]) e inicializar ([`init_settings()`]) los nuevos ajustes con tipos seguros y +//! valores predefinidos ([`predefined_settings!`](crate::predefined_settings)): //! //! ``` -//! use pagetop::config_map; +//! use pagetop::prelude::*; //! use serde::Deserialize; +//! use std::fmt::Debug; //! //! #[derive(Debug, Deserialize)] -//! pub struct Section1 { -//! pub var1: String, -//! pub var2: u16, +//! pub struct Id { +//! pub name: String, +//! pub desc: String, //! } //! //! #[derive(Debug, Deserialize)] -//! pub struct MySettings { -//! pub section1: Section1, +//! pub struct Size { +//! pub width: u16, +//! pub height: u16, //! } //! -//! config_map!("Application settings.", MYSETTINGS, MySettings); +//! #[derive(Debug, Deserialize)] +//! pub struct MyApp { +//! pub id: Id, +//! pub size: Size, +//! } +//! +//! pub static MY_APP: LazyStatic = LazyStatic::new(|| { +//! init_settings::(predefined_settings!( +//! // [id] +//! "id.name" => "Value Name", +//! "id.desc" => "Value Description", +//! +//! // [size] +//! "size.width" => "900", +//! "size.height" => "320" +//! )) +//! }); //! ``` //! -//! Use the first argument of [`config_map!`] for documentation purposes. +//! Es importante inicializar todos los ajustes con valores predefinidos (aunque sea con valores +//! vacíos como *""* o *"0"*, por ejemplo) para evitar *panic!*'s no deseados. //! -//! 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: +//! +//! # Cómo obtener los valores de configuración +//! +//! Basta con acceder directamente a la variable estática. Por ejemplo, con [`SETTINGS`]: //! //! ``` -//! config_map!(r#" -//! My configuration settings for *section1* section. -//! "#, -//! MYSETTINGS, -//! MySettings, -//! "section1.var1" => "seven", -//! "section1.var2" => 7 -//! ); +//! use pagetop::prelude::*; +//! +//! fn demo() { +//! println!("App name: {}", &SETTINGS.app.name); +//! println!("App description: {}", &SETTINGS.app.description); +//! println!("Value of PAGETOP_RUN_MODE: {}", &SETTINGS.app.run_mode); +//! } //! ``` +//! O a valores específicos de la configuración de tu **aplicación** o **módulo**: //! -//! # 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); -//! } -//! ``` +//! ``` +//! fn demo() { +//! println!("{}", &MY_APP.id.name); +//! println!("{}", &MY_APP.size.width); +//! } +//! ``` mod data; mod de; @@ -197,25 +131,45 @@ mod path; mod source; mod value; -use crate::{trace, LazyStatic}; +use crate::LazyStatic; use crate::config::data::ConfigData; use crate::config::file::File; -use serde::Deserialize; - use std::env; use std::fmt::Debug; -use std::str::FromStr; +use std::collections::HashMap; + +use serde::Deserialize; + +/// Un *HashMap* con una lista de literales `"clave" => "valor"` para asignar ajustes de +/// configuración predefinidos. +/// +/// Ver [`cómo añadir ajustes de configuración`](index.html#cómo-añadir-ajustes-de-configuración). +pub type PredefinedSettings = HashMap<&'static str, &'static str>; + +#[macro_export] +/// Macro para crear e inicializar un *HashMap* ([`PredefinedSettings`]) con una lista de literales +/// `"clave" => "valor"` para asignar ajustes de configuración predefinidos. +/// +/// Ver [`cómo añadir ajustes de configuración`](config/index.html#cómo-añadir-ajustes-de-configuración). +macro_rules! predefined_settings { + ( $($key:literal => $value:literal),* ) => {{ + #[allow(unused_mut)] + let mut a = PredefinedSettings::new(); + $( + a.insert($key, $value); + )* + a + }}; +} /// Directorio donde se encuentran los archivos de configuración. const CONFIG_DIR: &str = "config"; -/// 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 = LazyStatic::new(|| { +/// Todos los valores originales de la configuración en forma de pares `clave = valor` recogidos de +/// los archivos de configuración. +static CONFIG_DATA: LazyStatic = 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()); @@ -224,7 +178,7 @@ pub static CONFIG: LazyStatic = LazyStatic::new(|| { // 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. + // Primero añade la configuración común a todos los entornos. Opcional. .merge(File::with_name(&format!("{}/{}.toml", CONFIG_DIR, "common")).required(false)) .unwrap() @@ -243,147 +197,137 @@ pub static CONFIG: LazyStatic = LazyStatic::new(|| { settings }); -#[macro_export] -/// Loads specific type-safe settings for your module or application in a structure similar to -/// [`SETTINGS`]. +/// Carga ajustes con tipos seguros y valores predefinidos para tu aplicación o módulo en una +/// estructura similiar a [`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), - - 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 (del tipo especificado) de una clave. -/// -/// See [`How to access configuration`](config/index.html#how-to-access-configuration). -pub fn get(key: &str) -> T where ::Err: Debug { - match CONFIG.get_str(key) { - Ok(value) => match value.parse::() { - Ok(value) => value, - _ => { - trace::warn!("Failed to parse value for key \"{}\"! Return default empty value", key); - Default::default() - } - }, - _ => { - trace::warn!("Can't get config value for key \"{}\"! Return default empty value", key); - Default::default() - } +/// Ver [`Cómo añadir ajustes de configuración`](index.html#cómo-añadir-ajustes-de-configuración). +pub fn init_settings(values: PredefinedSettings) -> T +where T: Deserialize<'static> +{ + let mut settings = CONFIG_DATA.clone(); + for (key, value) in values.iter() { + settings.set_default(*key, *value).unwrap(); + } + match settings.try_into() { + Ok(c) => c, + Err(e) => panic!("Error parsing settings: {}", e), } } -#[rustfmt::skip] #[derive(Debug, Deserialize)] +/// Sección *\[app\]* de la configuración global. pub struct App { - pub name : String, - pub description : String, - pub theme : String, - pub language : String, - pub direction : String, + /// Valor predefinido: *"PageTop Application"* + pub name: String, + /// Valor predefinido: *"Developed with the amazing PageTop framework."* + pub description: String, + /// Valor predefinido: *"Bootsier"* + pub theme: String, + /// Valor predefinido: *"en-US"* + pub language: String, + /// Valor predefinido: *"ltr"* + pub direction: String, + /// Valor predefinido: *"Slant"* pub startup_banner: String, - pub run_mode : String, + /// Valor predefinido: según variable de entorno PAGETOP_RUN_MODE, o *"default"* si no lo está + pub run_mode: String, } -#[rustfmt::skip] #[derive(Debug, Deserialize)] +/// Sección *\[log\]* de la configuración global. pub struct Log { - pub tracing : String, - pub rolling : String, - pub path : String, - pub prefix : String, - pub format : String, + /// Valor predefinido: *"Info"* + pub tracing: String, + /// Valor predefinido: *"Stdout"* + pub rolling: String, + /// Valor predefinido: *"log"* + pub path: String, + /// Valor predefinido: *"tracing.log"* + pub prefix: String, + /// Valor predefinido: *"Full"* + pub format: String, } -#[rustfmt::skip] #[derive(Debug, Deserialize)] +/// Sección *\[database\]* de la configuración global. 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, + /// Valor predefinido: *""* + pub db_type: String, + /// Valor predefinido: *""* + pub db_name: String, + /// Valor predefinido: *""* + pub db_user: String, + /// Valor predefinido: *""* + pub db_pass: String, + /// Valor predefinido: *"localhost"* + pub db_host: String, + /// Valor predefinido: *"0"* + pub db_port: u16, + /// Valor predefinido: *"5"* + pub max_pool_size: u32, } -#[rustfmt::skip] #[derive(Debug, Deserialize)] +/// Sección *\[webserver\]* de la configuración global. pub struct Webserver { - pub bind_address : String, - pub bind_port : u16, + /// Valor predefinido: *"localhost"* + pub bind_address: String, + /// Valor predefinido: *"8088"* + pub bind_port: u16, } -#[rustfmt::skip] #[derive(Debug, Deserialize)] +/// Sección *\[dev\]* de la configuración global. pub struct Dev { + /// Valor predefinido: *""* pub static_files : String, } -#[rustfmt::skip] #[derive(Debug, Deserialize)] +/// Ajustes globales para las secciones *\[app\]*, *\[log\]*, *\[database\]*, *\[webserver\]* y +/// *\[dev\]* requeridas por PageTop (ver [`SETTINGS`]). pub struct Settings { - pub app : App, - pub log : Log, - pub database : Database, - pub webserver : Webserver, - pub dev : Dev, + 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. +/// Instancia los ajustes globales para la estructura [`Settings`]. +/// +/// Ver [`Cómo obtener los valores de configuración`](index.html#cómo-obtener-los-valores-de-configuración). +pub static SETTINGS: LazyStatic = LazyStatic::new(|| { + init_settings::(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", -See [`How to access configuration`](index.html#how-to-access-configuration). -"#, - SETTINGS, Settings, + // [log] + "log.tracing" => "Info", + "log.rolling" => "Stdout", + "log.path" => "log", + "log.prefix" => "tracing.log", + "log.format" => "Full", - // [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", + // [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", - // [log] - "log.tracing" => "Info", - "log.rolling" => "Stdout", - "log.path" => "log", - "log.prefix" => "tracing.log", - "log.format" => "Full", + // [webserver] + "webserver.bind_address" => "localhost", + "webserver.bind_port" => "8088", - // [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" => "" -); + // [dev] + "dev.static_files" => "" + )) +}); diff --git a/pagetop/src/core/module/all.rs b/pagetop/src/core/module/all.rs index 12e461d1..3fa88947 100644 --- a/pagetop/src/core/module/all.rs +++ b/pagetop/src/core/module/all.rs @@ -85,15 +85,6 @@ pub fn register_actions() { } } -// INIT SETTINGS *********************************************************************************** -/* -pub fn init_settings() { - trace::info!("initializing custom predefined settings"); - for m in ENABLED_MODULES.read().unwrap().iter() { - settings::add_predefined_settings(m.settings()); - } -} -*/ // INIT MODULES ************************************************************************************ pub fn init_modules() { diff --git a/pagetop/src/core/module/definition.rs b/pagetop/src/core/module/definition.rs index 110fe9bb..16d9d712 100644 --- a/pagetop/src/core/module/definition.rs +++ b/pagetop/src/core/module/definition.rs @@ -41,11 +41,7 @@ pub trait ModuleTrait: BaseModule + Send + Sync { fn actions(&self) -> Vec { vec![] } -/* - fn settings(&self) -> PredefinedSettings { - predefined_settings![] - } -*/ + fn init(&self) {} #[cfg(feature = "database")] diff --git a/pagetop/src/lib.rs b/pagetop/src/lib.rs index e9b2c650..a61ee3ad 100644 --- a/pagetop/src/lib.rs +++ b/pagetop/src/lib.rs @@ -40,7 +40,6 @@ // GLOBAL. pub use concat_string::concat_string; -pub use doc_comment::doc_comment; pub use once_cell::sync::Lazy as LazyStatic; // LOCAL.