♻️ Migra API pública de actix-web a Axum
- `configure_service` como `configure_router(Router) -> Router`. - Macro `static_files_service!` como `serve_static_files!`. - `ResultPage<M, E>` eliminado; handlers devuelven `Result<M, E>`. - `ErrorPage` implementa `IntoResponse` en lugar de `ResponseError`. - Registro con `OnceLock`; eliminados `drop_extensions` y `app.welcome`. - `Redirect` devuelve `Response`; docs y ejemplos actualizados.
This commit is contained in:
parent
019961ed77
commit
c1afe0e70c
30 changed files with 393 additions and 355 deletions
|
|
@ -1,60 +1,43 @@
|
|||
use crate::core::action::add_action;
|
||||
use crate::core::extension::ExtensionRef;
|
||||
use crate::core::theme::all::THEMES;
|
||||
use crate::{global, service, static_files_service, trace};
|
||||
use crate::web::Router;
|
||||
use crate::{global, serve_static_files, trace, web};
|
||||
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use std::sync::LazyLock;
|
||||
|
||||
// **< EXTENSIONES >********************************************************************************
|
||||
|
||||
static ENABLED_EXTENSIONS: LazyLock<RwLock<Vec<ExtensionRef>>> =
|
||||
LazyLock::new(|| RwLock::new(Vec::new()));
|
||||
|
||||
static DROPPED_EXTENSIONS: LazyLock<RwLock<Vec<ExtensionRef>>> =
|
||||
LazyLock::new(|| RwLock::new(Vec::new()));
|
||||
static EXTENSIONS: OnceLock<Vec<ExtensionRef>> = OnceLock::new();
|
||||
|
||||
// **< REGISTRO DE LAS EXTENSIONES >****************************************************************
|
||||
|
||||
pub fn register_extensions(root_extension: Option<ExtensionRef>) {
|
||||
// Prepara la lista de extensiones habilitadas.
|
||||
let mut enabled_list: Vec<ExtensionRef> = Vec::new();
|
||||
// Garantiza que ocurre sólo una vez cuando los tests se ejecutan en paralelo.
|
||||
EXTENSIONS.get_or_init(|| {
|
||||
let mut list: Vec<ExtensionRef> = Vec::new();
|
||||
|
||||
// Primero añade el tema básico a la lista de extensiones habilitadas.
|
||||
add_to_enabled(&mut enabled_list, &crate::base::theme::Basic);
|
||||
// Primero añade el tema básico a la lista de extensiones habilitadas.
|
||||
add_to_enabled(&mut list, &crate::base::theme::Basic);
|
||||
|
||||
// Si se proporciona una extensión raíz inicial, se añade a la lista de extensiones habilitadas.
|
||||
if let Some(extension) = root_extension {
|
||||
add_to_enabled(&mut enabled_list, extension);
|
||||
}
|
||||
// Si se proporciona la extensión raíz inicial, se añade a las extensiones habilitadas.
|
||||
if let Some(extension) = root_extension {
|
||||
add_to_enabled(&mut list, extension);
|
||||
}
|
||||
|
||||
// Añade la página de bienvenida predefinida si se habilita en la configuración.
|
||||
if global::SETTINGS.app.welcome {
|
||||
add_to_enabled(&mut enabled_list, &crate::base::extension::Welcome);
|
||||
}
|
||||
// Añade la página de bienvenida si no hay extensión raíz.
|
||||
if root_extension.is_none() {
|
||||
add_to_enabled(&mut list, &crate::base::extension::Welcome);
|
||||
}
|
||||
|
||||
// Guarda la lista final de extensiones habilitadas.
|
||||
ENABLED_EXTENSIONS.write().append(&mut enabled_list);
|
||||
|
||||
// Prepara una lista de extensiones deshabilitadas.
|
||||
let mut dropped_list: Vec<ExtensionRef> = Vec::new();
|
||||
|
||||
// Si se proporciona una extensión raíz, analiza su lista de dependencias.
|
||||
if let Some(extension) = root_extension {
|
||||
add_to_dropped(&mut dropped_list, extension);
|
||||
}
|
||||
|
||||
// Guarda la lista final de extensiones deshabilitadas.
|
||||
DROPPED_EXTENSIONS.write().append(&mut dropped_list);
|
||||
list
|
||||
});
|
||||
}
|
||||
|
||||
fn add_to_enabled(list: &mut Vec<ExtensionRef>, extension: ExtensionRef) {
|
||||
// Verifica que la extensión no esté en la lista para evitar duplicados.
|
||||
if !list.iter().any(|e| e.type_id() == extension.type_id()) {
|
||||
// Añade primero (en orden inverso) las dependencias de la extensión.
|
||||
for d in extension.dependencies().iter().rev() {
|
||||
add_to_enabled(list, *d);
|
||||
for d in extension.dependencies().into_iter().rev() {
|
||||
add_to_enabled(list, d);
|
||||
}
|
||||
|
||||
// Añade la propia extensión a la lista.
|
||||
|
|
@ -77,40 +60,11 @@ fn add_to_enabled(list: &mut Vec<ExtensionRef>, extension: ExtensionRef) {
|
|||
}
|
||||
}
|
||||
|
||||
fn add_to_dropped(list: &mut Vec<ExtensionRef>, extension: ExtensionRef) {
|
||||
// Recorre las extensiones que la actual recomienda deshabilitar.
|
||||
for d in &extension.drop_extensions() {
|
||||
// Verifica que la extensión no esté ya en la lista.
|
||||
if !list.iter().any(|e| e.type_id() == d.type_id()) {
|
||||
// Comprueba si la extensión está habilitada. Si es así, registra una advertencia.
|
||||
if ENABLED_EXTENSIONS
|
||||
.read()
|
||||
.iter()
|
||||
.any(|e| e.type_id() == extension.type_id())
|
||||
{
|
||||
trace::warn!(
|
||||
"Trying to drop \"{}\" extension which is enabled",
|
||||
extension.short_name()
|
||||
);
|
||||
} else {
|
||||
// Si la extensión no está habilitada, se añade a la lista y registra la acción.
|
||||
list.push(*d);
|
||||
trace::debug!("Extension \"{}\" dropped", d.short_name());
|
||||
// Añade recursivamente las dependencias de la extensión eliminada.
|
||||
// De este modo, todas las dependencias se tienen en cuenta para ser deshabilitadas.
|
||||
for dependency in &extension.dependencies() {
|
||||
add_to_dropped(list, *dependency);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// **< REGISTRO DE LAS ACCIONES >*******************************************************************
|
||||
|
||||
pub fn register_actions() {
|
||||
for extension in ENABLED_EXTENSIONS.read().iter() {
|
||||
for a in extension.actions().into_iter() {
|
||||
for extension in EXTENSIONS.get().into_iter().flatten() {
|
||||
for a in extension.actions() {
|
||||
add_action(a);
|
||||
}
|
||||
}
|
||||
|
|
@ -120,25 +74,28 @@ pub fn register_actions() {
|
|||
|
||||
pub fn initialize_extensions() {
|
||||
trace::info!("Calling application bootstrap");
|
||||
for extension in ENABLED_EXTENSIONS.read().iter() {
|
||||
extension.initialize();
|
||||
for e in EXTENSIONS.get().into_iter().flatten() {
|
||||
e.initialize();
|
||||
}
|
||||
}
|
||||
|
||||
// **< CONFIGURA LOS SERVICIOS >********************************************************************
|
||||
// **< CONFIGURA LAS RUTAS >************************************************************************
|
||||
|
||||
pub fn configure_services(scfg: &mut service::web::ServiceConfig) {
|
||||
pub fn configure_routes(router: Router) -> Router {
|
||||
// Sólo compila durante el desarrollo, para evitar errores 400 en la traza de eventos.
|
||||
#[cfg(debug_assertions)]
|
||||
scfg.route(
|
||||
// Ruta automática lanzada por Chrome DevTools.
|
||||
let router = router.route(
|
||||
"/.well-known/appspecific/com.chrome.devtools.json",
|
||||
service::web::get().to(|| async { service::HttpResponse::NotFound().finish() }),
|
||||
web::get(|| async { web::http::StatusCode::NOT_FOUND }),
|
||||
);
|
||||
|
||||
for extension in ENABLED_EXTENSIONS.read().iter() {
|
||||
extension.configure_service(scfg);
|
||||
}
|
||||
let router = EXTENSIONS
|
||||
.get()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.fold(router, |r, e| e.configure_router(r));
|
||||
|
||||
static_files_service!(scfg, [&global::SETTINGS.dev.pagetop_static_dir, assets] => "/");
|
||||
serve_static_files!(router, [&global::SETTINGS.dev.pagetop_static_dir, assets] => "/pagetop");
|
||||
|
||||
router
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
use crate::actions;
|
||||
use crate::core::AnyInfo;
|
||||
use crate::core::action::ActionBox;
|
||||
use crate::core::theme::ThemeRef;
|
||||
use crate::core::AnyInfo;
|
||||
use crate::locale::L10n;
|
||||
use crate::{actions, service};
|
||||
use crate::web::Router;
|
||||
|
||||
/// Interfaz común que debe implementar cualquier extensión de PageTop.
|
||||
///
|
||||
|
|
@ -11,15 +12,15 @@ use crate::{actions, service};
|
|||
///
|
||||
/// ```rust
|
||||
/// # use pagetop::prelude::*;
|
||||
/// pub struct Blog;
|
||||
/// pub struct MyExtension;
|
||||
///
|
||||
/// impl Extension for Blog {
|
||||
/// impl Extension for MyExtension {
|
||||
/// fn name(&self) -> L10n {
|
||||
/// L10n::n("Blog")
|
||||
/// L10n::n("My Extension")
|
||||
/// }
|
||||
///
|
||||
/// fn description(&self) -> L10n {
|
||||
/// L10n::n("Blog system")
|
||||
/// L10n::n("Does something useful")
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
|
|
@ -86,31 +87,95 @@ pub trait Extension: AnyInfo + Send + Sync {
|
|||
/// aceptar cualquier petición HTTP.
|
||||
fn initialize(&self) {}
|
||||
|
||||
/// Configura los servicios web de la extensión, como rutas, *middleware*, acceso a ficheros
|
||||
/// estáticos, etc., usando [`ServiceConfig`](crate::service::web::ServiceConfig).
|
||||
/// Registra rutas, servicios y capas de la extensión en el servidor web de la aplicación.
|
||||
///
|
||||
/// # Ejemplo
|
||||
/// Recibe las rutas acumuladas hasta ese momento, añade lo que la extensión necesite y retorna
|
||||
/// las rutas con las nuevas modificaciones. La implementación por defecto devuelve las rutas
|
||||
/// sin cambios.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// # Operaciones disponibles
|
||||
///
|
||||
/// | Operación | Llamada sobre `router` |
|
||||
/// |------------------------------------|-------------------------------------------------|
|
||||
/// | Ruta HTTP | `.route("/path", web::get(handler))` |
|
||||
/// | Rutas bajo prefijo común | `.nest("/prefix", sub_router)` |
|
||||
/// | Archivos estáticos | `serve_static_files!(router, [...] => "/path")` |
|
||||
/// | Capa de *middleware* | `.layer(some_layer)` |
|
||||
/// | Estado compartido entre *handlers* | `.with_state(my_state)` |
|
||||
///
|
||||
/// # Ejemplos
|
||||
///
|
||||
/// ## Rutas HTTP básicas
|
||||
///
|
||||
/// ```rust
|
||||
/// # use pagetop::prelude::*;
|
||||
/// pub struct ExtensionSample;
|
||||
/// # async fn list_posts() -> &'static str { "" }
|
||||
/// # async fn view_post() -> &'static str { "" }
|
||||
/// # async fn create_post() -> &'static str { "" }
|
||||
/// pub struct Blog;
|
||||
///
|
||||
/// impl Extension for ExtensionSample {
|
||||
/// fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {
|
||||
/// scfg.route("/sample", web::get().to(route_sample));
|
||||
/// impl Extension for Blog {
|
||||
/// fn configure_router(&self, router: Router) -> Router {
|
||||
/// router
|
||||
/// .route("/posts", web::get(list_posts))
|
||||
/// .route("/posts/{id}", web::get(view_post))
|
||||
/// .route("/posts/new", web::post(create_post))
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[allow(unused_variables)]
|
||||
fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {}
|
||||
|
||||
/// Permite declarar extensiones destinadas a deshabilitar o desinstalar recursos de otras
|
||||
/// extensiones asociadas a versiones anteriores de la aplicación.
|
||||
///
|
||||
/// Actualmente PageTop no utiliza este método, pero se reserva como *placeholder* para futuras
|
||||
/// implementaciones.
|
||||
fn drop_extensions(&self) -> Vec<ExtensionRef> {
|
||||
vec![]
|
||||
/// ## Rutas agrupadas bajo un prefijo
|
||||
///
|
||||
/// ```rust
|
||||
/// # use pagetop::prelude::*;
|
||||
/// # async fn dashboard() -> &'static str { "" }
|
||||
/// # async fn list_users() -> &'static str { "" }
|
||||
/// pub struct Admin;
|
||||
///
|
||||
/// impl Extension for Admin {
|
||||
/// fn configure_router(&self, router: Router) -> Router {
|
||||
/// let admin = Router::new()
|
||||
/// .route("/dashboard", web::get(dashboard))
|
||||
/// .route("/users", web::get(list_users));
|
||||
///
|
||||
/// router.nest("/admin", admin)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Rutas con capa de *middleware*
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// # use pagetop::prelude::*;
|
||||
/// pub struct Api;
|
||||
///
|
||||
/// impl Extension for Api {
|
||||
/// fn configure_router(&self, router: Router) -> Router {
|
||||
/// router
|
||||
/// .route("/api/data", web::get(get_data))
|
||||
/// .layer(auth_layer())
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Archivos estáticos
|
||||
///
|
||||
/// La macro [`serve_static_files!`](crate::serve_static_files) sombrea `router` internamente,
|
||||
/// por lo que el parámetro no necesita `mut`. Sí es necesario devolverlo al final.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// # use pagetop::prelude::*;
|
||||
/// pub struct MyExtension;
|
||||
///
|
||||
/// impl Extension for MyExtension {
|
||||
/// fn configure_router(&self, router: Router) -> Router {
|
||||
/// serve_static_files!(router, [assets] => "/static");
|
||||
/// router
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
fn configure_router(&self, router: Router) -> Router {
|
||||
router
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue