From cdc9357b5addf9a4af25c56db4f7c8ca60970694 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Mon, 15 Dec 2025 17:24:00 +0100 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=9A=A7=20[config]=20Nueva=20gesti?= =?UTF-8?q?=C3=B3n=20de=20opciones=20enumeradas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.rs | 2 +- src/app/figfont.rs | 22 ++++--------- src/global.rs | 39 +++++------------------ src/global/lang_negotiation.rs | 56 ++++++++++++++++++++++++++++++++++ src/global/startup_banner.rs | 50 ++++++++++++++++++++++++++++++ 5 files changed, 121 insertions(+), 48 deletions(-) create mode 100644 src/global/lang_negotiation.rs create mode 100644 src/global/startup_banner.rs diff --git a/src/app.rs b/src/app.rs index dd1f8780..b49e522e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -78,7 +78,7 @@ impl Application { use colored::Colorize; use terminal_size::{terminal_size, Width}; - if global::SETTINGS.app.startup_banner.to_lowercase() != "off" { + if global::SETTINGS.app.startup_banner != global::StartupBanner::Off { // Nombre de la aplicación, ajustado al ancho del terminal si es necesario. let mut app_ff = String::new(); let app_name = &global::SETTINGS.app.name; diff --git a/src/app/figfont.rs b/src/app/figfont.rs index 6592d813..1b3b2f7d 100644 --- a/src/app/figfont.rs +++ b/src/app/figfont.rs @@ -10,21 +10,11 @@ pub static FIGFONT: LazyLock = LazyLock::new(|| { let speed = include_str!("speed.flf"); let starwars = include_str!("starwars.flf"); - FIGfont::from_content( - match global::SETTINGS.app.startup_banner.to_lowercase().as_str() { - "off" => slant, - "slant" => slant, - "small" => small, - "speed" => speed, - "starwars" => starwars, - _ => { - println!( - "\n FIGfont \"{}\" not found for banner. Using \"Slant\". Check settings.", - global::SETTINGS.app.startup_banner, - ); - slant - } - }, - ) + FIGfont::from_content(match global::SETTINGS.app.startup_banner { + global::StartupBanner::Off | global::StartupBanner::Slant => slant, + global::StartupBanner::Small => small, + global::StartupBanner::Speed => speed, + global::StartupBanner::Starwars => starwars, + }) .unwrap() }); diff --git a/src/global.rs b/src/global.rs index b484731b..539e15f9 100644 --- a/src/global.rs +++ b/src/global.rs @@ -1,9 +1,15 @@ //! Opciones de configuración globales. -use crate::{include_config, AutoDefault}; +use crate::include_config; use serde::Deserialize; +mod lang_negotiation; +pub use lang_negotiation::LangNegotiation; + +mod startup_banner; +pub use startup_banner::StartupBanner; + // **< SETTINGS >*********************************************************************************** include_config!(SETTINGS: Settings => [ @@ -32,35 +38,6 @@ include_config!(SETTINGS: Settings => [ "server.session_lifetime" => 604_800, ]); -// **< LangNegotiation >**************************************************************************** - -/// Modos disponibles para negociar el idioma de una petición HTTP. -/// -/// El ajuste [`global::SETTINGS.app.lang_negotiation`](crate::global::App::lang_negotiation) -/// determina qué fuentes intervienen en la resolución del idioma efectivo utilizado por -/// [`RequestLocale`](crate::locale::RequestLocale) y en la generación de URLs mediante -/// [`Context::route()`](crate::core::component::Context::route). -#[derive(AutoDefault, Clone, Copy, Debug, Deserialize, Eq, PartialEq)] -pub enum LangNegotiation { - /// Usa todas las fuentes disponibles para determinar el idioma, en este orden: comprueba el - /// parámetro `?lang` de la URL; si no está presente o no es válido, usa la cabecera HTTP - /// `Accept-Language`; si tampoco está disponible o no es válido, usa el idioma configurado en - /// [`global::SETTINGS.app.language`](crate::global::App::language) o, en su defecto, el idioma - /// de respaldo. Es el comportamiento por defecto. - #[default] - Full, - - /// Igual que `LangNegotiation::Full`, pero sin tener en cuenta el parámetro `?lang` de la URL. - /// El idioma depende únicamente de la cabecera `Accept-Language` del navegador y, en última - /// instancia, de la configuración o idioma de respaldo. - NoQuery, - - /// Usa sólo la configuración o, en su defecto, el idioma de respaldo; ignora la cabecera - /// `Accept-Language` y el parámetro de la URL. Este modo proporciona un comportamiento estable - /// con idioma fijo. - ConfigOnly, -} - // **< Settings >*********************************************************************************** #[derive(Debug, Deserialize)] @@ -101,7 +78,7 @@ pub struct App { pub lang_negotiation: LangNegotiation, /// Banner ASCII mostrado al inicio: *"Off"* (desactivado), *"Slant"*, *"Small"*, *"Speed"* o /// *"Starwars"*. - pub startup_banner: String, + pub startup_banner: StartupBanner, /// Activa la página de bienvenida de PageTop. /// /// Si está activada, se instala la extensión [`Welcome`](crate::base::extension::Welcome), que diff --git a/src/global/lang_negotiation.rs b/src/global/lang_negotiation.rs new file mode 100644 index 00000000..1e24e6c4 --- /dev/null +++ b/src/global/lang_negotiation.rs @@ -0,0 +1,56 @@ +use crate::AutoDefault; + +use serde::{Deserialize, Deserializer}; + +/// Modos disponibles para negociar el idioma de una petición HTTP. +/// +/// El ajuste [`global::SETTINGS.app.lang_negotiation`](crate::global::App::lang_negotiation) +/// determina qué fuentes intervienen en la resolución del idioma efectivo utilizado por +/// [`RequestLocale`](crate::locale::RequestLocale) y en la generación de URLs mediante +/// [`Context::route()`](crate::core::component::Context::route). +#[derive(AutoDefault, Clone, Copy, Debug, Eq, PartialEq)] +pub enum LangNegotiation { + /// Usa todas las fuentes disponibles para determinar el idioma, en este orden: comprueba el + /// parámetro `?lang` de la URL; si no está presente o no es válido, usa la cabecera HTTP + /// `Accept-Language`; si tampoco está disponible o no es válido, usa el idioma configurado en + /// [`global::SETTINGS.app.language`](crate::global::App::language) o, en su defecto, el idioma + /// de respaldo. Es el comportamiento por defecto. + #[default] + Full, + + /// Igual que `LangNegotiation::Full`, pero sin tener en cuenta el parámetro `?lang` de la URL. + /// El idioma depende únicamente de la cabecera `Accept-Language` del navegador y, en última + /// instancia, de la configuración o idioma de respaldo. + NoQuery, + + /// Usa sólo la configuración o, en su defecto, el idioma de respaldo; ignora la cabecera + /// `Accept-Language` y el parámetro de la URL. Este modo proporciona un comportamiento estable + /// con idioma fijo. + ConfigOnly, +} + +impl<'de> Deserialize<'de> for LangNegotiation { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let raw = String::deserialize(deserializer)?; + let result = match raw.trim().to_ascii_lowercase().as_str() { + "full" => Self::Full, + "noquery" => Self::NoQuery, + "configonly" => Self::ConfigOnly, + _ => { + let default = Self::default(); + println!( + concat!( + "\nInvalid value \"{}\" for [app].lang_negotiation. ", + "Using \"{:?}\". Check settings.", + ), + raw, default, + ); + default + } + }; + Ok(result) + } +} diff --git a/src/global/startup_banner.rs b/src/global/startup_banner.rs new file mode 100644 index 00000000..d1d10688 --- /dev/null +++ b/src/global/startup_banner.rs @@ -0,0 +1,50 @@ +use crate::AutoDefault; + +use serde::{Deserialize, Deserializer}; + +/// Opciones para el *banner* ASCII mostrado al arrancar la aplicación. +/// +/// Se obtiene de [`global::SETTINGS.app.startup_banner`](crate::global::App::startup_banner) y +/// controla si se muestra un *banner* en la salida estándar al arrancar la aplicación. +#[derive(AutoDefault, Clone, Copy, Debug, Eq, PartialEq)] +pub enum StartupBanner { + /// No muestra ningún banner de inicio. + Off, + /// Banner en estilo "Slant". Es el comportamiento por defecto. + #[default] + Slant, + /// Banner en estilo "Small". + Small, + /// Banner en estilo "Speed". + Speed, + /// Banner en estilo "Starwars". + Starwars, +} + +impl<'de> Deserialize<'de> for StartupBanner { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let raw = String::deserialize(deserializer)?; + let result = match raw.trim().to_ascii_lowercase().as_str() { + "off" => Self::Off, + "slant" => Self::Slant, + "small" => Self::Small, + "speed" => Self::Speed, + "starwars" => Self::Starwars, + _ => { + let default = Self::default(); + println!( + concat!( + "\nInvalid value \"{}\" for [app].startup_banner. ", + "Using \"{:?}\". Check settings.", + ), + raw, default, + ); + default + } + }; + Ok(result) + } +} From c985744ab0b5e3912d0d409530a9dd6ba557f6a3 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Mon, 15 Dec 2025 20:51:23 +0100 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=9A=A7=20[config]=20Opciones=20enumer?= =?UTF-8?q?adas=20para=20el=20log?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/global.rs | 10 ++++++-- src/global/log_format.rs | 51 +++++++++++++++++++++++++++++++++++++++ src/global/log_rolling.rs | 51 +++++++++++++++++++++++++++++++++++++++ src/trace.rs | 45 ++++++++++++---------------------- 4 files changed, 126 insertions(+), 31 deletions(-) create mode 100644 src/global/log_format.rs create mode 100644 src/global/log_rolling.rs diff --git a/src/global.rs b/src/global.rs index 539e15f9..8bf753e3 100644 --- a/src/global.rs +++ b/src/global.rs @@ -10,6 +10,12 @@ pub use lang_negotiation::LangNegotiation; mod startup_banner; pub use startup_banner::StartupBanner; +mod log_rolling; +pub use log_rolling::LogRolling; + +mod log_format; +pub use log_format::LogFormat; + // **< SETTINGS >*********************************************************************************** include_config!(SETTINGS: Settings => [ @@ -114,13 +120,13 @@ pub struct Log { pub tracing: String, /// Muestra los mensajes de traza en el terminal (*"Stdout"*) o los vuelca en archivos con /// rotación: *"Daily"*, *"Hourly"*, *"Minutely"* o *"Endless"*. - pub rolling: String, + pub rolling: LogRolling, /// Directorio para los archivos de traza (si [`rolling`](Self::rolling) ≠ *"Stdout"*). pub path: String, /// Prefijo para los archivos de traza (si [`rolling`](Self::rolling) ≠ *"Stdout"*). pub prefix: String, /// Formato de salida de las trazas. Opciones: *"Full"*, *"Compact"*, *"Pretty"* o *"Json"*. - pub format: String, + pub format: LogFormat, } #[derive(Debug, Deserialize)] diff --git a/src/global/log_format.rs b/src/global/log_format.rs new file mode 100644 index 00000000..795d319d --- /dev/null +++ b/src/global/log_format.rs @@ -0,0 +1,51 @@ +use crate::AutoDefault; + +use serde::{Deserialize, Deserializer}; + +/// Formatos disponibles para mostrar las trazas. +/// +/// El valor se obtiene de [`global::SETTINGS.log.format`](crate::global::Log::format) y determina +/// la representación textual de los eventos registrados por `tracing`. +/// El valor configurado no distingue entre mayúsculas y minúsculas. +#[derive(AutoDefault, Clone, Copy, Debug, Eq, PartialEq)] +pub enum LogFormat { + /// Formato JSON estructurado. + Json, + + /// Formato completo con detalles adicionales. Es el valor por defecto. + #[default] + Full, + + /// Formato más conciso y legible. + Compact, + + /// Formato human-friendly con colores y saltos de línea. + Pretty, +} + +impl<'de> Deserialize<'de> for LogFormat { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let raw = String::deserialize(deserializer)?; + let result = match raw.trim().to_ascii_lowercase().as_str() { + "json" => Self::Json, + "full" => Self::Full, + "compact" => Self::Compact, + "pretty" => Self::Pretty, + _ => { + let default = Self::default(); + println!( + concat!( + "\nInvalid value \"{}\" for [log].format. ", + "Using \"{:?}\". Check settings.", + ), + raw, default, + ); + default + } + }; + Ok(result) + } +} diff --git a/src/global/log_rolling.rs b/src/global/log_rolling.rs new file mode 100644 index 00000000..b9b59c79 --- /dev/null +++ b/src/global/log_rolling.rs @@ -0,0 +1,51 @@ +use crate::AutoDefault; + +use serde::{Deserialize, Deserializer}; + +/// Modos de salida y rotación para el registro de trazas. +/// +/// El valor se obtiene de [`global::SETTINGS.log.rolling`](crate::global::Log::rolling) y +/// determina si las trazas se muestran por pantalla o se vuelcan en archivos con rotación. +/// El valor configurado no distingue entre mayúsculas y minúsculas. +#[derive(AutoDefault, Clone, Copy, Debug, Eq, PartialEq)] +pub enum LogRolling { + /// Escribe las trazas en la salida estándar (sin rotación de archivos). + Stdout, + /// Rotación diaria de archivos de traza. + #[default] + Daily, + /// Rotación horaria de archivos de traza. + Hourly, + /// Rotación por minutos de archivos de traza. + Minutely, + /// Archivo de traza "infinito", sin rotación. + Endless, +} + +impl<'de> Deserialize<'de> for LogRolling { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let raw = String::deserialize(deserializer)?; + let result = match raw.trim().to_ascii_lowercase().as_str() { + "stdout" => Self::Stdout, + "daily" => Self::Daily, + "hourly" => Self::Hourly, + "minutely" => Self::Minutely, + "endless" => Self::Endless, + _ => { + let default = Self::default(); + println!( + concat!( + "\nInvalid value \"{}\" for [log].rolling. ", + "Using \"{:?}\". Check settings.", + ), + raw, default, + ); + default + } + }; + Ok(result) + } +} diff --git a/src/trace.rs b/src/trace.rs index 12e428a8..461c2604 100644 --- a/src/trace.rs +++ b/src/trace.rs @@ -14,6 +14,7 @@ //! *spans* son estructurados, con la capacidad de registrar tipos de datos y mensajes de texto. use crate::global; +use crate::global::{LogFormat, LogRolling}; pub use tracing::{debug, error, info, trace, warn}; pub use tracing::{debug_span, error_span, info_span, trace_span, warn_span}; @@ -33,7 +34,6 @@ use std::sync::LazyLock; /// Dado que las trazas o eventos registrados poco antes de un fallo suelen ser cruciales para /// diagnosticar la causa, `Lazy` garantiza que todos los registros almacenados se /// envíen antes de finalizar la ejecución. -#[rustfmt::skip] pub(crate) static TRACING: LazyLock = LazyLock::new(|| { if !global::SETTINGS.log.enabled || cfg!(test) || cfg!(feature = "testing") { // Tracing desactivado, se instala un subscriber nulo. @@ -46,25 +46,19 @@ pub(crate) static TRACING: LazyLock = LazyLock::new(|| { let env_filter = EnvFilter::try_new(&global::SETTINGS.log.tracing) .unwrap_or_else(|_| EnvFilter::new("Info")); - let rolling = global::SETTINGS.log.rolling.to_lowercase(); + let rolling = global::SETTINGS.log.rolling; - let (non_blocking, guard) = match rolling.as_str() { - "stdout" => tracing_appender::non_blocking(std::io::stdout()), + let (non_blocking, guard) = match rolling { + LogRolling::Stdout => tracing_appender::non_blocking(std::io::stdout()), _ => tracing_appender::non_blocking({ let path = &global::SETTINGS.log.path; let prefix = &global::SETTINGS.log.prefix; - match rolling.as_str() { - "daily" => tracing_appender::rolling::daily(path, prefix), - "hourly" => tracing_appender::rolling::hourly(path, prefix), - "minutely" => tracing_appender::rolling::minutely(path, prefix), - "endless" => tracing_appender::rolling::never(path, prefix), - _ => { - println!( - "Rolling value \"{}\" not valid. Using \"daily\". Check the settings file.", - global::SETTINGS.log.rolling, - ); - tracing_appender::rolling::daily(path, prefix) - } + match rolling { + LogRolling::Daily => tracing_appender::rolling::daily(path, prefix), + LogRolling::Hourly => tracing_appender::rolling::hourly(path, prefix), + LogRolling::Minutely => tracing_appender::rolling::minutely(path, prefix), + LogRolling::Endless => tracing_appender::rolling::never(path, prefix), + LogRolling::Stdout => unreachable!("Stdout rolling already handled above"), } }), }; @@ -72,20 +66,13 @@ pub(crate) static TRACING: LazyLock = LazyLock::new(|| { let subscriber = tracing_subscriber::fmt() .with_env_filter(env_filter) .with_writer(non_blocking) - .with_ansi(rolling.as_str() == "stdout"); + .with_ansi(matches!(rolling, LogRolling::Stdout)); - match global::SETTINGS.log.format.to_lowercase().as_str() { - "json" => subscriber.json().init(), - "full" => subscriber.init(), - "compact" => subscriber.compact().init(), - "pretty" => subscriber.pretty().init(), - _ => { - println!( - "Tracing format \"{}\" not valid. Using \"Full\". Check the settings file.", - global::SETTINGS.log.format, - ); - subscriber.init(); - } + match global::SETTINGS.log.format { + LogFormat::Json => subscriber.json().init(), + LogFormat::Full => subscriber.init(), + LogFormat::Compact => subscriber.compact().init(), + LogFormat::Pretty => subscriber.pretty().init(), } guard