Añade API para extensiones con funcionalidades

Añade el interfaz común que debe implementar cualquier extensión de
PageTop para añadir nuevas funcionalidades a la aplicación en forma de
servicios web y API de uso.
This commit is contained in:
Manuel Cillero 2025-07-15 20:12:15 +02:00
parent 5d2f293942
commit 0df5f3f1c7
7 changed files with 436 additions and 2 deletions

104
src/core/extension/all.rs Normal file
View file

@ -0,0 +1,104 @@
use crate::core::extension::ExtensionRef;
use crate::{service, trace};
use std::sync::{LazyLock, RwLock};
// 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()));
// 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();
// 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);
}
// Guarda la lista final de extensiones habilitadas.
ENABLED_EXTENSIONS
.write()
.unwrap()
.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()
.unwrap()
.append(&mut dropped_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);
}
// Añade la propia extensión a la lista.
list.push(extension);
}
}
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()
.unwrap()
.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);
}
}
}
}
}
// INICIALIZA LAS EXTENSIONES **********************************************************************
pub fn initialize_extensions() {
trace::info!("Calling application bootstrap");
for extension in ENABLED_EXTENSIONS.read().unwrap().iter() {
extension.initialize();
}
}
// CONFIGURA LOS SERVICIOS *************************************************************************
pub fn configure_services(scfg: &mut service::web::ServiceConfig) {
for extension in ENABLED_EXTENSIONS.read().unwrap().iter() {
extension.configure_service(scfg);
}
}

View file

@ -0,0 +1,77 @@
use crate::core::AnyInfo;
use crate::locale::L10n;
use crate::service;
/// Representa una referencia a una extensión.
///
/// Las extensiones se definen como instancias estáticas globales para poder acceder a ellas desde
/// cualquier hilo de la ejecución sin necesidad de sincronización adicional.
pub type ExtensionRef = &'static dyn ExtensionTrait;
/// Interfaz común que debe implementar cualquier extensión de `PageTop`.
///
/// Este *trait* es fácil de implementar, basta con declarar la estructura de la extensión y
/// sobreescribir los métodos que sea necesario.
///
/// ```rust
/// use pagetop::prelude::*;
///
/// pub struct Blog;
///
/// impl ExtensionTrait for Blog {
/// fn name(&self) -> L10n { L10n::n("Blog") }
/// fn description(&self) -> L10n { L10n::n("Sistema de blogs") }
/// }
/// ```
pub trait ExtensionTrait: AnyInfo + Send + Sync {
/// Nombre legible para el usuario.
///
/// Predeterminado por el [`short_name`](AnyInfo::short_name) del tipo asociado a la extensión.
fn name(&self) -> L10n {
L10n::n(self.short_name())
}
/// Descripción corta para paneles, listados, etc.
fn description(&self) -> L10n {
L10n::default()
}
/// Otras extensiones que deben habilitarse **antes** de esta.
///
/// `PageTop` las resolverá automáticamente respetando el orden durante el arranque de la
/// aplicación.
fn dependencies(&self) -> Vec<ExtensionRef> {
vec![]
}
/// Inicializa la extensión durante la lógica de arranque de la aplicación.
///
/// Se llama una sola vez, después de que todas las dependencias se han inicializado y antes de
/// 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).
///
/// ```rust,ignore
/// use pagetop::prelude::*;
///
/// pub struct ExtensionSample;
///
/// impl ExtensionTrait for ExtensionSample {
/// fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {
/// scfg.route("/sample", web::get().to(route_sample));
/// }
/// }
/// ```
#[allow(unused_variables)]
fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {}
/// Permite crear extensiones para deshabilitar y desinstalar los recursos de otras extensiones
/// utilizadas en versiones anteriores de la aplicación.
///
/// Actualmente no se usa, pero se deja como *placeholder* para futuras implementaciones.
fn drop_extensions(&self) -> Vec<ExtensionRef> {
vec![]
}
}