diff --git a/Cargo.toml b/Cargo.toml index 1b07ae3a..67ed8c44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,15 +24,18 @@ categories = [ ] [dependencies] +doc-comment = "0.3.3" once_cell = "1.9.0" +config_rs = { package = "config", version = "0.11.0", features = ["toml"] } actix-web = "4.0.0-rc.3" sycamore = { version = "0.8.0-beta.2", features = ["ssr"] } +tokio = { version = "1.16", features = ["macros", "rt-multi-thread"] } +serde = { version = "1.0", features = ["derive"] } + [lib] name = "pagetop" -path = "src/lib.rs" [[bin]] name = "pagetop" -path = "src/main.rs" diff --git a/STARTER.Cargo.toml b/STARTER.Cargo.toml index 4795fde9..13ef591d 100644 --- a/STARTER.Cargo.toml +++ b/STARTER.Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" [dependencies] pagetop = { path = "pagetop" } -actix-web = "4.0.0-rc.3" +tokio = { version = "1.16", features = ["macros", "rt-multi-thread"] } [[bin]] name = "app" diff --git a/config/default.toml b/config/default.toml new file mode 100644 index 00000000..429d6da5 --- /dev/null +++ b/config/default.toml @@ -0,0 +1,2 @@ +[app] +name = "PageTop Essence" diff --git a/config/settings.default.toml b/config/settings.default.toml new file mode 100644 index 00000000..cef2d7cf --- /dev/null +++ b/config/settings.default.toml @@ -0,0 +1,9 @@ +[app] +name = "PageTop Application" +description = "Developed with the amazing PageTop framework." + +[webserver] +# Configuración opcional del servidor web. +# Usar bind_address = "" para deshabilitar el servidor web. +bind_address = "localhost" +bind_port = 8088 diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 00000000..afefaf4f --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,5 @@ +/// Nombre del directorio donde se encuentra la configuración. +pub const CONFIG_DIR: &'static str = "config"; + +mod settings; +pub use crate::config::settings::{CONFIG, SETTINGS}; diff --git a/src/config/settings.rs b/src/config/settings.rs new file mode 100644 index 00000000..ffb316f6 --- /dev/null +++ b/src/config/settings.rs @@ -0,0 +1,103 @@ +use crate::Lazy; +use crate::config::CONFIG_DIR; + +use config_rs::{Config, File}; +use serde::Deserialize; + +use std::env; + +/// Carga los ajustes globales "clave = valor" al arrancar la aplicación. +pub static CONFIG: Lazy = Lazy::new(|| { + // Establece el modo de ejecución según el valor de la variable de entorno + // PAGETOP_RUN_MODE. Asume "default" por defecto. + let run_mode = env::var("PAGETOP_RUN_MODE").unwrap_or("default".into()); + + // Inicializa los ajustes. + let mut settings = Config::default(); + + // Lee los ajustes combinando los archivos de configuración disponibles y + // asigna el modo de ejecución. + settings + .merge( + File::with_name( + &format!("{}/{}.toml", CONFIG_DIR, "common") + ).required(false)).unwrap() + .merge( + File::with_name( + &format!("{}/{}.toml", CONFIG_DIR, run_mode) + ).required(false)).unwrap() + .merge( + File::with_name( + &format!("{}/{}.toml", CONFIG_DIR, "local") + ).required(false)).unwrap() + .set("app.run_mode", run_mode).unwrap(); + + settings +}); + +#[macro_export] +/// Usar esta macro para obtener el valor de cualquier ajuste global, donde +/// clave y valor son cadenas de caracteres. Devuelve la cadena vacía si no +/// encuentra un ajuste para la clave. +macro_rules! config_get { + ( $key:expr ) => { + $crate::config::CONFIG.get_str($key).unwrap_or("".to_string()) + }; +} + +#[macro_export] +/// Carga los ajustes específicos de tu módulo o aplicación en una estructura +/// similar a [`SETTINGS`] con tipos de variables seguros. Genera un *panic!* +/// en caso de asignaciones no válidas. +macro_rules! config_map { + ( $COMMENT:expr, $CONF:ident, $TYPE:tt $(, $key:expr => $value:expr)* ) => { + $crate::doc_comment! { + concat!($COMMENT), + + pub static $CONF: $crate::Lazy<$TYPE> = $crate::Lazy::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), + } + }); + } + }; +} + +#[derive(Debug, Deserialize)] +pub struct App { + pub name : String, + pub description : String, + pub run_mode : String, +} + +#[derive(Debug, Deserialize)] +pub struct Webserver { + pub bind_address : String, + pub bind_port : u16, +} + +#[derive(Debug, Deserialize)] +pub struct Settings { + pub app : App, + pub webserver : Webserver, +} + +config_map!(r#" +Ajustes globales y valores por defecto para las secciones *\[app\]* y +*\[webserver\]* específicas de PageTop. +"#, + SETTINGS, Settings, + + // [app] + "app.name" => "PageTop Application", + "app.description" => "Developed with the amazing PageTop framework.", + + // [webserver] + "webserver.bind_address" => "localhost", + "webserver.bind_port" => 8088 +); diff --git a/src/core/module/api.rs b/src/core/module/api.rs index 37c84786..bbbdb5eb 100644 --- a/src/core/module/api.rs +++ b/src/core/module/api.rs @@ -1,6 +1,6 @@ use crate::core::server; -/// Modules must implement this trait. +/// Los módulos deben implementar este *trait*. pub trait Module: Send + Sync { fn name(&self) -> String; diff --git a/src/core/server/main.rs b/src/core/server/main.rs index 457bb652..3f5a7d90 100644 --- a/src/core/server/main.rs +++ b/src/core/server/main.rs @@ -1,16 +1,21 @@ +use crate::config::SETTINGS; use crate::core::{Server, all, server}; pub fn run(bootstrap: Option) -> Result { - // Call application bootstrap. + // Ejecuta la función de inicio específica para la aplicación. if bootstrap != None { let _ = &(bootstrap.unwrap())(); } + // Inicializa el servidor web. let server = server::HttpServer::new(|| { server::App::new() .configure(&all::modules) }) - .bind("127.0.0.1:8000")? + .bind(format!("{}:{}", + &SETTINGS.webserver.bind_address, + &SETTINGS.webserver.bind_port + ))? .run(); Ok(server) } diff --git a/src/core/state.rs b/src/core/state.rs index 6ded57b8..5535c3b6 100644 --- a/src/core/state.rs +++ b/src/core/state.rs @@ -4,7 +4,7 @@ use crate::core::module::Module; use std::sync::RwLock; // ----------------------------------------------------------------------------- -// Registered modules. +// Módulos registrados. // ----------------------------------------------------------------------------- pub static MODULES: Lazy>> = Lazy::new(|| { diff --git a/src/lib.rs b/src/lib.rs index 01361a13..15be4ce1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,11 @@ // Global. +pub use doc_comment::doc_comment; pub use once_cell::sync::Lazy; -pub mod core; +// ----------------------------------------------------------------------------- +// APIs públicas. +// ----------------------------------------------------------------------------- -pub use actix_web::main; +pub mod config; // Gestión de la configuración. +pub mod core; // Servidor web y sistemas para Temas, Módulos y Respuestas. diff --git a/src/main.rs b/src/main.rs index c1545d0a..4d3e46b9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +use pagetop::config_get; use pagetop::core::module::Module; use pagetop::core::{register_module, server}; @@ -16,7 +17,7 @@ impl Module for Greet { } async fn greet() -> impl server::Responder { - "Hello!" + format!("Hello from {}!", config_get!("app.name")) } struct GreetWithParam; @@ -46,7 +47,7 @@ fn bootstrap() { register_module(&GreetWithParam); } -#[pagetop::main] +#[tokio::main] async fn main() -> std::io::Result<()> { server::run(Some(bootstrap))?.await } diff --git a/tests/health_check.rs b/tests/health_check.rs index 4ae6653e..0c309bea 100644 --- a/tests/health_check.rs +++ b/tests/health_check.rs @@ -1,5 +1,7 @@ +use pagetop::core::server; + fn spawn_app() { - let server = pagetop::core::server::run().expect("Failed to bind address"); + let server = server::run(None).expect("Failed to bind address"); let _ = tokio::spawn(server); }