diff --git a/src/app.rs b/src/app.rs index b49e522e..dd1f8780 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 != global::StartupBanner::Off { + if global::SETTINGS.app.startup_banner.to_lowercase() != "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 1b3b2f7d..6592d813 100644 --- a/src/app/figfont.rs +++ b/src/app/figfont.rs @@ -10,11 +10,21 @@ 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 { - global::StartupBanner::Off | global::StartupBanner::Slant => slant, - global::StartupBanner::Small => small, - global::StartupBanner::Speed => speed, - global::StartupBanner::Starwars => starwars, - }) + 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 + } + }, + ) .unwrap() }); diff --git a/src/global.rs b/src/global.rs index 8bf753e3..b484731b 100644 --- a/src/global.rs +++ b/src/global.rs @@ -1,21 +1,9 @@ //! Opciones de configuración globales. -use crate::include_config; +use crate::{include_config, AutoDefault}; use serde::Deserialize; -mod lang_negotiation; -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 => [ @@ -44,6 +32,35 @@ 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)] @@ -84,7 +101,7 @@ pub struct App { pub lang_negotiation: LangNegotiation, /// Banner ASCII mostrado al inicio: *"Off"* (desactivado), *"Slant"*, *"Small"*, *"Speed"* o /// *"Starwars"*. - pub startup_banner: StartupBanner, + pub startup_banner: String, /// Activa la página de bienvenida de PageTop. /// /// Si está activada, se instala la extensión [`Welcome`](crate::base::extension::Welcome), que @@ -120,13 +137,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: LogRolling, + pub rolling: String, /// 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: LogFormat, + pub format: String, } #[derive(Debug, Deserialize)] diff --git a/src/global/lang_negotiation.rs b/src/global/lang_negotiation.rs deleted file mode 100644 index 1e24e6c4..00000000 --- a/src/global/lang_negotiation.rs +++ /dev/null @@ -1,56 +0,0 @@ -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/log_format.rs b/src/global/log_format.rs deleted file mode 100644 index 795d319d..00000000 --- a/src/global/log_format.rs +++ /dev/null @@ -1,51 +0,0 @@ -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 deleted file mode 100644 index b9b59c79..00000000 --- a/src/global/log_rolling.rs +++ /dev/null @@ -1,51 +0,0 @@ -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/global/startup_banner.rs b/src/global/startup_banner.rs deleted file mode 100644 index d1d10688..00000000 --- a/src/global/startup_banner.rs +++ /dev/null @@ -1,50 +0,0 @@ -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) - } -} diff --git a/src/trace.rs b/src/trace.rs index 461c2604..12e428a8 100644 --- a/src/trace.rs +++ b/src/trace.rs @@ -14,7 +14,6 @@ //! *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}; @@ -34,6 +33,7 @@ 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,19 +46,25 @@ 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; + let rolling = global::SETTINGS.log.rolling.to_lowercase(); - let (non_blocking, guard) = match rolling { - LogRolling::Stdout => tracing_appender::non_blocking(std::io::stdout()), + let (non_blocking, guard) = match rolling.as_str() { + "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 { - 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"), + 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) + } } }), }; @@ -66,13 +72,20 @@ pub(crate) static TRACING: LazyLock = LazyLock::new(|| { let subscriber = tracing_subscriber::fmt() .with_env_filter(env_filter) .with_writer(non_blocking) - .with_ansi(matches!(rolling, LogRolling::Stdout)); + .with_ansi(rolling.as_str() == "stdout"); - match global::SETTINGS.log.format { - LogFormat::Json => subscriber.json().init(), - LogFormat::Full => subscriber.init(), - LogFormat::Compact => subscriber.compact().init(), - LogFormat::Pretty => subscriber.pretty().init(), + 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(); + } } guard