pagetop/src/config.rs
2024-02-16 20:03:11 +01:00

351 lines
13 KiB
Rust

//! Retrieve and apply settings values from configuration files.
//!
//! 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 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 cargar los ajustes de configuración
//!
//! Si tu aplicación requiere archivos de configuración debes crear un directorio *config* al mismo
//! nivel del archivo *Cargo.toml* de tu proyecto (o del ejecutable binario de la aplicación).
//!
//! PageTop se encargará de cargar todos los ajustes de configuración de tu aplicación leyendo los
//! siguientes archivos TOML en este orden (todos los archivos son opcionales):
//!
//! 1. **config/common.toml**, útil para los ajustes comunes a cualquier entorno. Estos valores
//! podrán ser sobrescritos al fusionar los archivos de configuración restantes.
//!
//! 2. **config/{file}.toml**, donde *{file}* se define con la variable de entorno
//! `PAGETOP_RUN_MODE`:
//!
//! * Si no está definida se asumirá *default* por defecto y PageTop intentará cargar el archivo
//! *config/default.toml* si existe.
//!
//! * De esta manera podrás 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.
//!
//! * Normalmente estos archivos suelen ser idóneos para incluir contraseñas o configuración
//! 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 de los archivos anteriores.
//!
//!
//! # Cómo añadir ajustes de configuración
//!
//! Para proporcionar a tu **módulo** sus propios ajustes de configuración, añade
//! [*serde*](https://docs.rs/serde) en las dependencias de tu archivo *Cargo.toml* habilitando la
//! característica `derive`:
//!
//! ```toml
//! [dependencies]
//! serde = { version = "1.0", features = ["derive"] }
//! ```
//!
//! Y luego inicializa con la macro [`default_settings!`](crate::default_settings) tus ajustes,
//! usando tipos seguros y asignando los valores predefinidos para la estructura asociada:
//!
//! ```
//! use pagetop::prelude::*;
//! use serde::Deserialize;
//!
//! #[derive(Debug, Deserialize)]
//! pub struct Settings {
//! pub myapp: MyApp,
//! }
//!
//! #[derive(Debug, Deserialize)]
//! pub struct MyApp {
//! pub name: String,
//! pub description: Option<String>,
//! pub width: u16,
//! pub height: u16,
//! }
//!
//! default_settings!(
//! // [myapp]
//! "myapp.name" => "Value Name",
//! "myapp.width" => 900,
//! "myapp.height" => 320,
//! );
//! ```
//!
//! De hecho, así se declaran los ajustes globales de la configuración (ver [`SETTINGS`]).
//!
//! Puedes usar la [sintaxis TOML](https://toml.io/en/v1.0.0#table) para añadir tu nueva sección
//! `[myapp]` en los archivos de configuración, del mismo modo que se añaden `[log]` o `[server]` en
//! los ajustes globales (ver [`Settings`]).
//!
//! Se recomienda inicializar todos los ajustes con valores predefinidos, o utilizar la notación
//! `Option<T>` si van a ser tratados en el código como opcionales.
//!
//! Si no pueden inicializarse correctamente los ajustes de configuración, entonces la aplicación
//! ejecutará un panic! y detendrá la ejecución.
//!
//! Los ajustes de configuración siempre serán de sólo lectura.
//!
//!
//! # Cómo usar tus nuevos ajustes de configuración
//!
//! ```
//! use pagetop::prelude::*;
//!
//! fn global_settings() {
//! println!("App name: {}", &config::SETTINGS.app.name);
//! println!("App description: {}", &config::SETTINGS.app.description);
//! println!("Value of PAGETOP_RUN_MODE: {}", &config::SETTINGS.app.run_mode);
//! }
//!
//! fn package_settings() {
//! println!("{} - {:?}", &SETTINGS.myapp.name, &SETTINGS.myapp.description);
//! println!("{}", &SETTINGS.myapp.width);
//! }
//! ```
mod data;
mod de;
mod error;
mod file;
mod path;
mod source;
mod value;
use crate::config::data::ConfigData;
use crate::config::file::File;
use crate::{concat_string, LazyStatic};
use serde::Deserialize;
use std::env;
/// Directorio donde se encuentran los archivos de configuración.
const CONFIG_DIR: &str = "config";
/// Valores originales de la configuración en forma de pares `clave = valor` recogidos de los
/// archivos de configuración.
#[rustfmt::skip]
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 mut settings = ConfigData::default();
// Combina los archivos (opcionales) de configuración y asigna el modo de ejecución.
settings
// Primero añade la configuración común a todos los entornos. Por defecto 'common.toml'.
.merge(
File::with_name(&concat_string!(CONFIG_DIR, "/common.toml"))
.required(false)
).unwrap()
// Añade la configuración específica del entorno. Por defecto 'default.toml'.
.merge(
File::with_name(&concat_string!(CONFIG_DIR, "/", run_mode, ".toml"))
.required(false)
).unwrap()
// Añade la configuración local reservada del entorno. Por defecto 'default.local.toml'.
.merge(
File::with_name(&concat_string!(CONFIG_DIR, "/local.", run_mode, ".toml"))
.required(false),
).unwrap()
// Añade la configuración local reservada general. Por defecto 'local.toml'.
.merge(
File::with_name(&concat_string!(CONFIG_DIR, "/local.toml"))
.required(false)
).unwrap()
// Salvaguarda el modo de ejecución.
.set("app.run_mode", run_mode)
.unwrap();
settings
});
#[macro_export]
/// Define un conjunto de ajustes de configuración usando tipos seguros y valores predefinidos.
///
/// Detiene la aplicación con un panic! si no pueden asignarse los ajustes de configuración.
///
/// Ver [`Cómo añadir ajustes de configuración`](config/index.html#cómo-añadir-ajustes-de-configuración).
macro_rules! default_settings {
( $($key:literal => $value:literal),* $(,)? ) => {
#[doc = concat!(
"Assigned or predefined values for configuration settings associated to the ",
"[`Settings`] type."
)]
pub static SETTINGS: $crate::LazyStatic<Settings> = $crate::LazyStatic::new(|| {
let mut settings = $crate::config::CONFIG.clone();
$(
settings.set_default($key, $value).unwrap();
)*
match settings.try_into() {
Ok(s) => s,
Err(e) => panic!("Error parsing settings: {}", e),
}
});
};
}
#[derive(Debug, Deserialize)]
/// Configuration settings for the [`[app]`](App), [`[database]`](Database), [`[dev]`](Dev),
/// [`[log]`](Log), and [`[server]`](Server) sections (see [`SETTINGS`]).
pub struct Settings {
pub app: App,
pub database: Database,
pub dev: Dev,
pub log: Log,
pub server: Server,
}
#[derive(Debug, Deserialize)]
/// Section `[app]` of the configuration settings.
///
/// See [`Settings`].
pub struct App {
/// El nombre de la aplicación.
/// Por defecto: *"PageTop App"*.
pub name: String,
/// Una descripción breve de la aplicación.
/// Por defecto: *"Developed with the amazing PageTop framework."*.
pub description: String,
/// Tema predeterminado.
/// Por defecto: *"Default"*.
pub theme: String,
/// Idioma (localización) predeterminado.
/// Por defecto: *"en-US"*.
pub language: String,
/// Dirección predeterminada para el texto: *"ltr"* (de izquierda a derecha), *"rtl"* (de
/// derecha a izquierda) o *"auto"*.
/// Por defecto: *"ltr"*.
pub direction: String,
/// Rótulo de texto ASCII al arrancar: *"Off"*, *"Slant"*, *"Small"*, *"Speed"* o *"Starwars"*.
/// Por defecto: *"Slant"*.
pub startup_banner: String,
/// Por defecto: según variable de entorno `PAGETOP_RUN_MODE`, o *"default"* si no lo está.
pub run_mode: String,
}
#[derive(Debug, Deserialize)]
/// Section `[database]` of the configuration settings.
///
/// See [`Settings`].
pub struct Database {
/// Tipo de base de datos: *"mysql"*, *"postgres"* ó *"sqlite"*.
/// Por defecto: *""*.
pub db_type: String,
/// Nombre (para mysql/postgres) o referencia (para sqlite) de la base de datos.
/// Por defecto: *""*.
pub db_name: String,
/// Usuario de conexión a la base de datos (para mysql/postgres).
/// Por defecto: *""*.
pub db_user: String,
/// Contraseña para la conexión a la base de datos (para mysql/postgres).
/// Por defecto: *""*.
pub db_pass: String,
/// Servidor de conexión a la base de datos (para mysql/postgres).
/// Por defecto: *"localhost"*.
pub db_host: String,
/// Puerto de conexión a la base de datos, normalmente 3306 (para mysql) ó 5432 (para postgres).
/// Por defecto: *0*.
pub db_port: u16,
/// Número máximo de conexiones habilitadas.
/// Por defecto: *5*.
pub max_pool_size: u32,
}
#[derive(Debug, Deserialize)]
/// Section `[dev]` of the configuration settings.
///
/// See [`Settings`].
pub struct Dev {
/// Los archivos estáticos requeridos por la aplicación se integran de manera predeterminada en
/// el binario ejecutable. Sin embargo, durante el desarrollo puede resultar útil servir estos
/// archivos desde su propio directorio para evitar recompilar cada vez que se modifican. En
/// este caso bastaría con indicar la ruta completa al directorio raíz del proyecto.
/// Por defecto: *""*.
pub pagetop_project_dir: String,
}
#[derive(Debug, Deserialize)]
/// Section `[log]` of the configuration settings.
///
/// See [`Settings`].
pub struct Log {
/// Filtro, o combinación de filtros separados por coma, para la traza de ejecución: *"Error"*,
/// *"Warn"*, *"Info"*, *"Debug"* o *"Trace"*.
/// Por ejemplo: "Error,actix_server::builder=Info,tracing_actix_web=Debug".
/// Por defecto: *"Info"*.
pub tracing: String,
/// Muestra la traza en el terminal (*"Stdout"*) o queda registrada en archivos con rotación
/// *"Daily"*, *"Hourly"*, *"Minutely"* o *"Endless"*.
/// Por defecto: *"Stdout"*.
pub rolling: String,
/// Directorio para los archivos de traza (si `rolling` != *"Stdout"*).
/// Por defecto: *"log"*.
pub path: String,
/// Prefijo para los archivos de traza (si `rolling` != *"Stdout"*).
/// Por defecto: *"tracing.log"*.
pub prefix: String,
/// Presentación de las trazas. Puede ser *"Full"*, *"Compact"*, *"Pretty"* o *"Json"*.
/// Por defecto: *"Full"*.
pub format: String,
}
#[derive(Debug, Deserialize)]
/// Section `[server]` of the configuration settings.
///
/// See [`Settings`].
pub struct Server {
/// Dirección del servidor web.
/// Por defecto: *"localhost"*.
pub bind_address: String,
/// Puerto del servidor web.
/// Por defecto: *8088*.
pub bind_port: u16,
/// Duración en segundos para la sesión (0 indica "hasta que se cierre el navegador").
/// Por defecto: *604800* (7 días).
pub session_lifetime: i64,
}
default_settings!(
// [app]
"app.name" => "PageTop App",
"app.description" => "Developed with the amazing PageTop framework.",
"app.theme" => "Default",
"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,
// [dev]
"dev.pagetop_project_dir" => "",
// [log]
"log.tracing" => "Info",
"log.rolling" => "Stdout",
"log.path" => "log",
"log.prefix" => "tracing.log",
"log.format" => "Full",
// [server]
"server.bind_address" => "localhost",
"server.bind_port" => 8088,
"server.session_lifetime" => 604800,
);