Añade lectura de configuración global y modular

- Soporta jerarquía de ficheros TOML que mapean ajustes a estructuras
  fuertemente tipadas con valores predefinidos.
- Permite definir configuraciones distintas para cada entorno.
- Añade la macro `include_config!` para facilitar la asignación modular
  de ajustes de configuración.
- Añade documentación detallada y tests de verificación.
This commit is contained in:
Manuel Cillero 2025-07-05 22:23:05 +02:00
parent cbee4c2cb8
commit f7dbd90af2
14 changed files with 4938 additions and 4 deletions

5
.gitignore vendored
View file

@ -1,2 +1,7 @@
# Ignora directorios de compilación
**/target
# Archivos de configuración locales
**/local.*.toml
**/local.toml
.env

141
Cargo.lock generated
View file

@ -319,6 +319,27 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
[[package]]
name = "colored"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "config"
version = "0.15.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "595aae20e65c3be792d05818e8c63025294ac3cb7e200f11459063a352a6ef80"
dependencies = [
"pathdiff",
"serde",
"toml",
"winnow",
]
[[package]]
name = "cookie"
version = "0.16.2"
@ -424,6 +445,22 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]]
name = "figlet-rs"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4742a071cd9694fc86f9fa1a08fa3e53d40cc899d7ee532295da2d085639fbc5"
[[package]]
name = "flate2"
version = "1.1.2"
@ -712,6 +749,12 @@ version = "0.2.174"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
[[package]]
name = "linux-raw-sys"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
[[package]]
name = "litemap"
version = "0.8.0"
@ -807,10 +850,16 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "pagetop"
version = "0.0.1"
version = "0.0.2"
dependencies = [
"actix-web",
"colored",
"config",
"figlet-rs",
"pagetop-macros",
"serde",
"substring",
"terminal_size",
]
[[package]]
@ -843,6 +892,12 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "pathdiff"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
[[package]]
name = "percent-encoding"
version = "2.3.1"
@ -994,6 +1049,19 @@ version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f"
[[package]]
name = "rustix"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.59.0",
]
[[package]]
name = "ryu"
version = "1.0.20"
@ -1038,6 +1106,15 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_spanned"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
dependencies = [
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
@ -1104,6 +1181,15 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "substring"
version = "1.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ee6433ecef213b2e72f587ef64a2f5943e7cd16fbd82dbe8bc07486c534c86"
dependencies = [
"autocfg",
]
[[package]]
name = "syn"
version = "2.0.104"
@ -1126,6 +1212,16 @@ dependencies = [
"syn",
]
[[package]]
name = "terminal_size"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed"
dependencies = [
"rustix",
"windows-sys 0.59.0",
]
[[package]]
name = "time"
version = "0.3.41"
@ -1197,6 +1293,40 @@ dependencies = [
"tokio",
]
[[package]]
name = "toml"
version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]]
name = "tracing"
version = "0.1.41"
@ -1367,6 +1497,15 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd"
dependencies = [
"memchr",
]
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"

View file

@ -1,6 +1,6 @@
[package]
name = "pagetop"
version = "0.0.1"
version = "0.0.2"
edition = "2021"
description = """\
@ -15,6 +15,13 @@ license.workspace = true
authors.workspace = true
[dependencies]
colored = "3.0.0"
config = { version = "0.15.11", default-features = false, features = ["toml"] }
figlet-rs = "0.1.5"
serde.workspace = true
substring = "1.4.5"
terminal_size = "0.4.2"
actix-web = "4.11.0"
pagetop-macros.workspace = true
@ -34,5 +41,7 @@ license = "MIT OR Apache-2.0"
authors = ["Manuel Cillero <manuel@cillero.es>"]
[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
# Helpers
pagetop-macros = { version = "0.0", path = "helpers/pagetop-macros" }

View file

@ -1,6 +1,10 @@
//! Prepara y ejecuta una aplicación creada con `Pagetop`.
use crate::service;
mod figfont;
use crate::{global, service};
use substring::Substring;
use std::io::Error;
@ -9,14 +13,61 @@ pub struct Application;
impl Application {
/// Crea una instancia de la aplicación.
pub fn new() -> Self {
// Al arrancar muestra una cabecera para la aplicación.
Self::show_banner();
Self
}
// Muestra una cabecera para la aplicación basada en la configuración.
fn show_banner() {
use colored::Colorize;
use terminal_size::{terminal_size, Width};
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;
if let Some((Width(term_width), _)) = terminal_size() {
if term_width >= 80 {
let maxlen: usize = ((term_width / 10) - 2).into();
let mut app = app_name.substring(0, maxlen).to_owned();
if app_name.len() > maxlen {
app = format!("{app}...");
}
if let Some(ff) = figfont::FIGFONT.convert(&app) {
app_ff = ff.to_string();
}
}
}
if app_ff.is_empty() {
println!("\n{app_name}");
} else {
print!("\n{app_ff}");
}
// Descripción de la aplicación.
if !global::SETTINGS.app.description.is_empty() {
println!("{}", global::SETTINGS.app.description.cyan());
};
// Versión de PageTop.
println!(
"{} {}\n",
"Powered by PageTop".yellow(),
env!("CARGO_PKG_VERSION").yellow()
);
}
}
/// Ejecuta el servidor web de la aplicación.
pub fn run(self) -> Result<service::Server, Error> {
// Prepara el servidor web.
Ok(service::HttpServer::new(move || Self::service_app())
.bind("localhost:8080")?
.bind(format!(
"{}:{}",
&global::SETTINGS.server.bind_address,
&global::SETTINGS.server.bind_port
))?
.run())
}

30
src/app/figfont.rs Normal file
View file

@ -0,0 +1,30 @@
use crate::global;
use std::sync::LazyLock;
use figlet_rs::FIGfont;
pub static FIGFONT: LazyLock<FIGfont> = LazyLock::new(|| {
let slant = include_str!("slant.flf");
let small = include_str!("small.flf");
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
}
},
)
.unwrap()
});

1295
src/app/slant.flf Normal file

File diff suppressed because it is too large Load diff

1097
src/app/small.flf Normal file

File diff suppressed because it is too large Load diff

1301
src/app/speed.flf Normal file

File diff suppressed because it is too large Load diff

719
src/app/starwars.flf Normal file
View file

@ -0,0 +1,719 @@
flf2a$ 7 6 22 15 4
starwars.flf by Ryan Youck (youck@cs.uregina.ca) Dec 25/1994
I am not responsible for use of this font
Based on Big.flf by Glenn Chappell
$ $@
$ $@
$ $@
$ $@
$ $@
$ $@
$ $@@
__ $@
| |$@
| |$@
| |$@
|__|$@
(__)$@
$@@
_ _ @
( | )@
V V @
$ @
$ @
$ @
@@
_ _ @
_| || |_$@
|_ __ _|@
_| || |_ @
|_ __ _|@
|_||_| $@
@@
__,--,_.@
/ |@
| (----`@
\ \ $@
.----) | $@
|_ __/ $@
'--' $@@
_ ___$ @
/ \ / /$ @
( o ) / / $ @
\_/ / / _$ @
/ / / \ @
/ / ( o )@
/__/ \_/ @@
@
___ @
( _ ) $@
/ _ \/\@
| (_> <@
\___/\/@
$@@
__ @
(_ )@
|/ @
$ @
$ @
$ @
@@
___@
/ /@
| |$@
| |$@
| |$@
| |$@
\__\@@
___ @
\ \ @
| |@
| |@
| |@
| |@
/__/ @@
_ @
/\| |/\ @
\ ` ' /$@
|_ _|@
/ , . \$@
\/|_|\/ @
@@
@
_ @
_| |_$@
|_ _|@
|_| $@
$ @
@@
@
@
$ @
$ @
__ @
(_ )@
|/ @@
@
@
______ @
|______|@
$ @
$ @
@@
@
@
@
$ @
__ @
(__)@
@@
___@
/ /@
/ / @
/ /$ @
/ /$ @
/__/$ @
@@
___ $@
/ _ \ $@
| | | |$@
| | | |$@
| |_| |$@
\___/ $@
$@@
__ $@
/_ |$@
| |$@
| |$@
| |$@
|_|$@
$@@
___ $@
|__ \ $@
$) |$@
/ / $@
/ /_ $@
|____|$@
$@@
____ $@
|___ \ $@
__) |$@
|__ < $@
___) |$@
|____/ $@
$@@
_ _ $@
| || | $@
| || |_ $@
|__ _|$@
| | $@
|_| $@
$@@
_____ $@
| ____|$@
| |__ $@
|___ \ $@
___) |$@
|____/ $@
$@@
__ $@
/ / $@
/ /_ $@
| '_ \ $@
| (_) |$@
\___/ $@
$@@
______ $@
|____ |$@
$/ / $@
/ / $@
/ / $@
/_/ $@
$@@
___ $@
/ _ \ $@
| (_) |$@
> _ < $@
| (_) |$@
\___/ $@
$@@
___ $@
/ _ \ $@
| (_) |$@
\__, |$@
/ / $@
/_/ $@
$@@
@
_ @
(_)@
$ @
_ @
(_)@
@@
@
_ @
(_)@
$ @
_ @
( )@
|/ @@
___@
/ /@
/ /$@
< <$ @
\ \$@
\__\@
@@
@
______ @
|______|@
______ @
|______|@
@
@@
___ @
\ \$ @
\ \ @
> >@
/ / @
/__/$ @
@@
______ $@
| \ $@
`----) |$@
/ / $@
|__| $@
__ $@
(__) $@@
____ @
/ __ \ @
/ / _` |@
| | (_| |@
\ \__,_|@
\____/ @
@@
___ $ @
/ \ $ @
/ ^ \$ @
/ /_\ \$ @
/ _____ \$ @
/__/ \__\$@
$@@
.______ $@
| _ \ $@
| |_) |$@
| _ < $@
| |_) |$@
|______/ $@
$@@
______$@
/ |@
| ,----'@
| | $@
| `----.@
\______|@
$@@
_______ $@
| \$@
| .--. |@
| | | |@
| '--' |@
|_______/$@
$@@
_______ @
| ____|@
| |__ $@
| __| $@
| |____ @
|_______|@
@@
_______ @
| ____|@
| |__ $@
| __| $@
| | $ @
|__| @
@@
_______ @
/ _____|@
| | __ $@
| | |_ |$@
| |__| |$@
\______|$@
$@@
__ __ $@
| | | |$@
| |__| |$@
| __ |$@
| | | |$@
|__| |__|$@
$@@
__ $@
| |$@
| |$@
| |$@
| |$@
|__|$@
$@@
__ $@
| |$@
| |$@
.--. | |$@
| `--' |$@
\______/ $@
$@@
__ ___$@
| |/ /$@
| ' / $@
| < $@
| . \ $@
|__|\__\$@
$@@
__ $@
| | $@
| | $@
| | $@
| `----.@
|_______|@
$@@
.___ ___.$@
| \/ |$@
| \ / |$@
| |\/| |$@
| | | |$@
|__| |__|$@
$@@
.__ __.$@
| \ | |$@
| \| |$@
| . ` |$@
| |\ |$@
|__| \__|$@
$@@
______ $@
/ __ \ $@
| | | |$@
| | | |$@
| `--' |$@
\______/ $@
$@@
.______ $@
| _ \ $@
| |_) |$@
| ___/ $@
| | $ @
| _| $ @
$ @@
______ $ @
/ __ \ $ @
| | | | $ @
| | | | $ @
| `--' '--. @
\_____\_____\@
$ @@
.______ $ @
| _ \ $ @
| |_) | $ @
| / $ @
| |\ \----.@
| _| `._____|@
$@@
_______.@
/ |@
| (----`@
\ \ $@
.----) | $@
|_______/ $@
$@@
.___________.@
| |@
`---| |----`@
| | $ @
| | $ @
|__| $ @
$ @@
__ __ $@
| | | |$@
| | | |$@
| | | |$@
| `--' |$@
\______/ $@
$@@
____ ____$@
\ \ / /$@
\ \/ /$ @
\ /$ @
\ /$ @
\__/$ @
$ @@
____ __ ____$@
\ \ / \ / /$@
\ \/ \/ /$ @
\ /$ @
\ /\ /$ @
\__/ \__/$ @
$ @@
___ ___$@
\ \ / /$@
\ V / $@
> < $@
/ . \ $@
/__/ \__\$@
$@@
____ ____$@
\ \ / /$@
\ \/ /$ @
\_ _/$ @
| |$ @
|__|$ @
$ @@
________ $@
| / $@
`---/ / $@
/ / $@
/ /----.@
/________|@
$@@
____ @
| |@
| |-`@
| | $@
| | $@
| |-.@
|____|@@
___ @
\ \ $ @
\ \$ @
\ \$ @
\ \$@
\__\@
@@
____ @
| |@
`-| |@
| |@
| |@
.-| |@
|____|@@
___ @
/ \ @
/--^--\@
$@
$@
$@
$@@
@
@
@
$ @
$ @
______ @
|______|@@
__ @
( _)@
\| @
$ @
$ @
$ @
@@
___ $ @
/ \ $ @
/ ^ \$ @
/ /_\ \$ @
/ _____ \$ @
/__/ \__\$@
$@@
.______ $@
| _ \ $@
| |_) |$@
| _ < $@
| |_) |$@
|______/ $@
$@@
______$@
/ |@
| ,----'@
| | $@
| `----.@
\______|@
$@@
_______ $@
| \$@
| .--. |@
| | | |@
| '--' |@
|_______/$@
$@@
_______ @
| ____|@
| |__ $@
| __| $@
| |____ @
|_______|@
@@
_______ @
| ____|@
| |__ $@
| __| $@
| | $ @
|__| @
@@
_______ @
/ _____|@
| | __ $@
| | |_ |$@
| |__| |$@
\______|$@
$@@
__ __ $@
| | | |$@
| |__| |$@
| __ |$@
| | | |$@
|__| |__|$@
$@@
__ $@
| |$@
| |$@
| |$@
| |$@
|__|$@
$@@
__ $@
| |$@
| |$@
.--. | |$@
| `--' |$@
\______/ $@
$@@
__ ___$@
| |/ /$@
| ' / $@
| < $@
| . \ $@
|__|\__\$@
$@@
__ $@
| | $@
| | $@
| | $@
| `----.@
|_______|@
$@@
.___ ___.$@
| \/ |$@
| \ / |$@
| |\/| |$@
| | | |$@
|__| |__|$@
$@@
.__ __.$@
| \ | |$@
| \| |$@
| . ` |$@
| |\ |$@
|__| \__|$@
$@@
______ $@
/ __ \ $@
| | | |$@
| | | |$@
| `--' |$@
\______/ $@
$@@
.______ $@
| _ \ $@
| |_) |$@
| ___/ $@
| | $ @
| _| $ @
$ @@
______ $ @
/ __ \ $ @
| | | | $ @
| | | | $ @
| `--' '--. @
\_____\_____\@
$ @@
.______ $ @
| _ \ $ @
| |_) | $ @
| / $ @
| |\ \----.@
| _| `._____|@
$@@
_______.@
/ |@
| (----`@
\ \ $@
.----) | $@
|_______/ $@
$@@
.___________.@
| |@
`---| |----`@
| | $ @
| | $ @
|__| $ @
$ @@
__ __ $@
| | | |$@
| | | |$@
| | | |$@
| `--' |$@
\______/ $@
$@@
____ ____$@
\ \ / /$@
\ \/ /$ @
\ /$ @
\ /$ @
\__/$ @
$ @@
____ __ ____$@
\ \ / \ / /$@
\ \/ \/ /$ @
\ /$ @
\ /\ /$ @
\__/ \__/$ @
$ @@
___ ___$@
\ \ / /$@
\ V / $@
> < $@
/ . \ $@
/__/ \__\$@
$@@
____ ____$@
\ \ / /$@
\ \/ /$ @
\_ _/$ @
| |$ @
|__|$ @
$ @@
________ $@
| / $@
`---/ / $@
/ / $@
/ /----.@
/________|@
$@@
___@
/ /@
| |$@
/ /$ @
\ \$ @
| |$@
\__\@@
__ $@
| |$@
| |$@
| |$@
| |$@
| |$@
|__|$@@
___ @
\ \$ @
| | @
\ \@
/ /@
| | @
/__/$ @@
__ _ @
/ \/ |@
|_/\__/ @
$ @
$ @
$ @
@@
_ _ @
(_)_(_) @
/ \ @
/ _ \ @
/ ___ \ @
/_/ \_\@
@@
_ _ @
(_)_(_)@
/ _ \ @
| | | |@
| |_| |@
\___/ @
@@
_ _ @
(_) (_)@
| | | |@
| | | |@
| |_| |@
\___/ @
@@
_ _ @
(_) (_)@
__ _ @
/ _` |@
| (_| |@
\__,_|@
@@
_ _ @
(_) (_)@
___ @
/ _ \ @
| (_) |@
\___/ @
@@
_ _ @
(_) (_)@
_ _ @
| | | |@
| |_| |@
\__,_|@
@@
___ @
/ _ \ @
| | ) |@
| |< < @
| | ) |@
| ||_/ @
|_| @@

179
src/config.rs Normal file
View file

@ -0,0 +1,179 @@
//! Carga las opciones de configuración.
//!
//! Estos ajustes se obtienen de archivos [TOML](https://toml.io) como pares `clave = valor` que se
//! mapean a estructuras **fuertemente tipadas** y valores predefinidos.
//!
//! Siguiendo la metodología [Twelve-Factor App](https://12factor.net/config), `PageTop` separa el
//! **código** de la **configuración**, lo que permite tener configuraciones diferentes para cada
//! despliegue, como *dev*, *staging* o *production*, sin modificar el código fuente.
//!
//!
//! # Orden de carga
//!
//! Si tu aplicación necesita archivos de configuración, crea un directorio `config` en la raíz del
//! proyecto, al mismo nivel que el archivo *Cargo.toml* o que el binario de la aplicación.
//!
//! `PageTop` carga en este orden, y siempre de forma opcional, los siguientes archivos TOML:
//!
//! 1. **config/common.toml**, para ajustes comunes a todos los entornos. Este enfoque simplifica el
//! mantenimiento al centralizar los valores de configuración comunes.
//!
//! 2. **config/{rm}.toml**, donde `{rm}` es el valor de la variable de entorno `PAGETOP_RUN_MODE`:
//!
//! * Si `PAGETOP_RUN_MODE` no está definida, se asume el valor `default`, y `PageTop` intentará
//! cargar *config/default.toml* si el archivo existe.
//!
//! * Útil para definir configuraciones específicas por entorno, garantizando que cada uno (p.e.
//! *dev*, *staging* o *production*) disponga de sus propias opciones, como claves de API,
//! URLs o ajustes de rendimiento, sin afectar a los demás.
//!
//! 3. **config/local.{rm}.toml**, útil para configuraciones locales específicas de la máquina o de
//! la ejecución:
//!
//! * Permite añadir o sobrescribir ajustes propios del entorno. Por ejemplo, `local.dev.toml`
//! para desarrollo o `local.production.toml` para retoques en producción.
//!
//! * Facilita que cada desarrollador adapte la configuración a su equipo en un entorno dado. Por
//! lo general no se comparte ni se sube al sistema de control de versiones.
//!
//! 4. **config/local.toml**, para ajustes locales válidos en cualquier entorno, ideal para cambios
//! rápidos o valores temporales que no dependan de un entorno concreto.
//!
//! Los archivos se combinan en el orden anterior, cada archivo sobrescribe a los anteriores en caso
//! de conflicto.
//!
//!
//! # Cómo añadir opciones de configuración a tu código
//!
//! Añade [*serde*](https://docs.rs/serde) en tu archivo *Cargo.toml* con la *feature* `derive`:
//!
//! ```toml
//! [dependencies]
//! serde = { version = "1.0", features = ["derive"] }
//! ```
//!
//! Y usa la macro [`include_config!`](crate::include_config) para inicializar tus ajustes en una
//! estructura con tipos seguros. Por ejemplo:
//!
//! ```rust#ignore
//! use pagetop::prelude::*;
//! use serde::Deserialize;
//!
//! include_config!(SETTINGS: Settings => [
//! // [myapp]
//! "myapp.name" => "Value Name",
//! "myapp.width" => 900,
//! "myapp.height" => 320,
//! ]);
//!
//! #[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,
//! }
//! ```
//!
//! De esta forma estás añadiendo una nueva sección `[myapp]` a la configuración, igual que existen
//! `[app]` o `[server]` en las opciones globales de [`Settings`](crate::global::Settings).
//!
//! Se recomienda proporcionar siempre valores por defecto o usar `Option<T>` para los ajustes
//! opcionales.
//!
//! Si la configuración no se inicializa correctamente, la aplicación lanzará *panic* y detendrá la
//! ejecución.
//!
//! Las estructuras de configuración son de **sólo lectura** durante la ejecución.
//!
//!
//! # Usando tus opciones de configuración
//!
//! ```rust#ignore
//! use pagetop::prelude::*;
//! use crate::config;
//!
//! fn global_settings() {
//! println!("Nombre de la app: {}", &global::SETTINGS.app.name);
//! println!("Descripción: {}", &global::SETTINGS.app.description);
//! println!("Run mode: {}", &global::SETTINGS.app.run_mode);
//! }
//!
//! fn extension_settings() {
//! println!("{} - {:?}", &config::SETTINGS.myapp.name, &config::SETTINGS.myapp.description);
//! println!("{}", &config::SETTINGS.myapp.width);
//! }
//! ```
use config::builder::DefaultState;
use config::{Config, ConfigBuilder, File};
use std::env;
use std::path::{Path, PathBuf};
use std::sync::LazyLock;
// Nombre del directorio de configuración por defecto.
const DEFAULT_CONFIG_DIR: &str = "config";
// Modo de ejecución por defecto.
const DEFAULT_RUN_MODE: &str = "default";
/// Valores originales cargados desde los archivos de configuración como pares `clave = valor`.
pub static CONFIG_VALUES: LazyLock<ConfigBuilder<DefaultState>> = LazyLock::new(|| {
// Determina el directorio de configuración:
// - Usa CONFIG_DIR si está definido en el entorno (p.e.: CONFIG_DIR=/etc/myapp ./myapp).
// - Si no, intenta DEFAULT_CONFIG_DIR dentro del proyecto (en CARGO_MANIFEST_DIR).
// - Si nada de esto aplica, entonces usa DEFAULT_CONFIG_DIR relativo al ejecutable.
let config_dir: PathBuf = if let Ok(env_dir) = env::var("CONFIG_DIR") {
env_dir.into()
} else if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
let manifest_config = Path::new(&manifest_dir).join(DEFAULT_CONFIG_DIR);
if manifest_config.exists() {
manifest_config
} else {
DEFAULT_CONFIG_DIR.into()
}
} else {
DEFAULT_CONFIG_DIR.into()
};
// Determina el modo de ejecución según la variable de entorno PAGETOP_RUN_MODE. Por defecto usa
// DEFAULT_RUN_MODE si no está definida (p.e.: PAGETOP_RUN_MODE=production ./myapp).
let rm = env::var("PAGETOP_RUN_MODE").unwrap_or_else(|_| DEFAULT_RUN_MODE.into());
Config::builder()
// 1. Configuración común para todos los entornos (common.toml).
.add_source(File::from(config_dir.join("common.toml")).required(false))
// 2. Configuración específica del entorno (p.e.: default.toml, production.toml).
.add_source(File::from(config_dir.join(format!("{rm}.toml"))).required(false))
// 3. Configuración local reservada para cada entorno (p.e.: local.default.toml).
.add_source(File::from(config_dir.join(format!("local.{rm}.toml"))).required(false))
// 4. Configuración local común (local.toml).
.add_source(File::from(config_dir.join("local.toml")).required(false))
// Guarda el modo de ejecución explícitamente.
.set_override("app.run_mode", rm)
.expect("Failed to set application run mode")
});
#[macro_export]
macro_rules! include_config {
( $SETTINGS:ident : $Settings:ty => [ $( $key:expr => $value:expr ),* $(,)? ] ) => {
/// Valores asignados o predefinidos para la configuración de [`$Settings`].
pub static $SETTINGS: std::sync::LazyLock<$Settings> = std::sync::LazyLock::new(|| {
let mut settings = $crate::config::CONFIG_VALUES.clone();
$(
settings = settings.set_default($key, $value).unwrap();
)*
settings
.build()
.expect(concat!("Failed to build config for ", stringify!($Settings)))
.try_deserialize::<$Settings>()
.expect(concat!("Error parsing settings for ", stringify!($Settings)))
});
};
}

57
src/global.rs Normal file
View file

@ -0,0 +1,57 @@
//! Opciones de configuración globales.
use crate::include_config;
use serde::Deserialize;
include_config!(SETTINGS: Settings => [
// [app]
"app.name" => "Sample",
"app.description" => "Developed with the amazing PageTop framework.",
"app.startup_banner" => "Slant",
// [server]
"server.bind_address" => "localhost",
"server.bind_port" => 8080,
]);
#[derive(Debug, Deserialize)]
/// Ajustes de configuración para las secciones globales [`[app]`](App) y [`[server]`](Server).
/// Consulta [`SETTINGS`] para los valores por defecto.
pub struct Settings {
pub app: App,
pub server: Server,
}
#[derive(Debug, Deserialize)]
/// Sección `[app]` de la configuración.
///
/// Forma parte de [`Settings`].
pub struct App {
/// Nombre de la aplicación.
/// Valor por defecto: *"Sample"*.
pub name: String,
/// Breve descripción de la aplicación.
/// Valor por defecto: *"Developed with the amazing PageTop framework."*.
pub description: String,
/// ASCII banner printed at startup: *"Off"*, *"Slant"*, *"Small"*, *"Speed"*, or *"Starwars"*.
/// Default: *"Slant"*.
pub startup_banner: String,
/// Modo de ejecución.
/// Valor por defecto: el definido por la variable de entorno
/// `PAGETOP_RUN_MODE`, o *"default"* si no está establecida.
pub run_mode: String,
}
#[derive(Debug, Deserialize)]
/// Sección `[server]` de la configuración.
///
/// Forma parte de [`Settings`].
pub struct Server {
/// Dirección de enlace para el servidor web.
/// Valor por defecto: *"localhost"*.
pub bind_address: String,
/// Puerto de escucha del servidor web.
/// Valor por defecto: *8088*.
pub bind_port: u16,
}

View file

@ -36,6 +36,10 @@ pub use pagetop_macros::{main, test};
// API *********************************************************************************************
// Carga las opciones de configuración.
pub mod config;
// Opciones de configuración globales.
pub mod global;
// Gestión del servidor y servicios web.
pub mod service;
// Prepara y ejecuta la aplicación.

View file

@ -4,8 +4,15 @@
pub use crate::{main, test};
// MACROS.
// crate::config
pub use crate::include_config;
// API.
pub use crate::global;
pub use crate::service;
pub use crate::app::Application;

41
tests/config.rs Normal file
View file

@ -0,0 +1,41 @@
use pagetop::prelude::*;
use serde::Deserialize;
use std::env;
include_config!(SETTINGS: Settings => [
"test.string_value" => "Test String",
"test.int_value" => -321,
"test.float_value" => 2.3456,
]);
#[derive(Debug, Deserialize)]
pub struct Settings {
pub test: Test,
}
#[derive(Debug, Deserialize)]
pub struct Test {
pub string_value: String,
pub int_value: i32,
pub float_value: f32,
}
#[pagetop::test]
async fn check_global_config() {
env::set_var("PAGETOP_RUN_MODE", "test");
assert_eq!(global::SETTINGS.app.run_mode, "test");
assert_eq!(global::SETTINGS.app.name, "Testing");
assert_eq!(global::SETTINGS.server.bind_port, 9000);
}
#[pagetop::test]
async fn check_local_config() {
env::set_var("PAGETOP_RUN_MODE", "test");
assert_eq!(SETTINGS.test.string_value, "Modified value");
assert_eq!(SETTINGS.test.int_value, -321);
assert_eq!(SETTINGS.test.float_value, 8.7654);
}