✨ Añade soporte para inyectar acciones en código
This commit is contained in:
parent
86e4c4f110
commit
613ab5243c
10 changed files with 316 additions and 4 deletions
|
@ -56,6 +56,9 @@ impl Application {
|
||||||
// Registra las extensiones de la aplicación.
|
// Registra las extensiones de la aplicación.
|
||||||
extension::all::register_extensions(root_extension);
|
extension::all::register_extensions(root_extension);
|
||||||
|
|
||||||
|
// Registra las acciones de las extensiones.
|
||||||
|
extension::all::register_actions();
|
||||||
|
|
||||||
// Inicializa las extensiones.
|
// Inicializa las extensiones.
|
||||||
extension::all::initialize_extensions();
|
extension::all::initialize_extensions();
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//! Tipos y funciones esenciales para crear extensiones y temas.
|
//! Tipos y funciones esenciales para crear acciones, extensiones y temas.
|
||||||
|
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|
||||||
|
@ -201,6 +201,9 @@ pub trait AnyCast: AnyInfo {
|
||||||
/// Implementación automática para cualquier tipo que ya cumpla [`AnyInfo`].
|
/// Implementación automática para cualquier tipo que ya cumpla [`AnyInfo`].
|
||||||
impl<T: ?Sized + AnyInfo> AnyCast for T {}
|
impl<T: ?Sized + AnyInfo> AnyCast for T {}
|
||||||
|
|
||||||
|
// API para definir acciones que alteran el comportamiento predeterminado del código.
|
||||||
|
pub mod action;
|
||||||
|
|
||||||
// API para añadir nuevas funcionalidades usando extensiones.
|
// API para añadir nuevas funcionalidades usando extensiones.
|
||||||
pub mod extension;
|
pub mod extension;
|
||||||
|
|
||||||
|
|
66
src/core/action.rs
Normal file
66
src/core/action.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
//! API para definir acciones que inyectan código en el flujo de la aplicación.
|
||||||
|
//!
|
||||||
|
//! Permite crear acciones en las librerías para que otros *crates* puedan inyectar código usando
|
||||||
|
//! funciones *ad hoc* que modifican el comportamiento predefinido en puntos concretos del flujo de
|
||||||
|
//! ejecución de la aplicación.
|
||||||
|
|
||||||
|
mod definition;
|
||||||
|
pub use definition::{ActionBase, ActionBox, ActionKey, ActionTrait};
|
||||||
|
|
||||||
|
mod list;
|
||||||
|
use list::ActionsList;
|
||||||
|
|
||||||
|
mod all;
|
||||||
|
pub(crate) use all::add_action;
|
||||||
|
pub use all::dispatch_actions;
|
||||||
|
|
||||||
|
/// Crea una lista de acciones para facilitar la implementación del método
|
||||||
|
/// [`actions`](crate::core::extension::ExtensionTrait#method.actions).
|
||||||
|
///
|
||||||
|
/// Esta macro crea vectores de [`ActionBox`], el tipo dinámico que encapsula cualquier acción que
|
||||||
|
/// implemente [`ActionTrait`]. Evita escribir repetidamente `Box::new(...)` para cada acción
|
||||||
|
/// inyectada, manteniendo el código más limpio.
|
||||||
|
///
|
||||||
|
/// # Ejemplos
|
||||||
|
///
|
||||||
|
/// Puede llamarse sin argumentos para crear un vector vacío:
|
||||||
|
///
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// let my_actions = inject_actions![];
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// O con una lista de acciones concretas:
|
||||||
|
///
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// let my_actions = inject_actions![
|
||||||
|
/// MyFirstAction::new(),
|
||||||
|
/// MySecondAction::new().with_weight(10),
|
||||||
|
/// ];
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Internamente, expande a un `vec![Box::new(...), ...]`.
|
||||||
|
///
|
||||||
|
/// # Ejemplo típico en una extensión
|
||||||
|
///
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// impl ExtensionTrait for MyExtension {
|
||||||
|
/// fn actions(&self) -> Vec<ActionBox> {
|
||||||
|
/// inject_actions![
|
||||||
|
/// CustomizeLoginAction::new(),
|
||||||
|
/// ModifyHeaderAction::new().with_weight(-5),
|
||||||
|
/// ]
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Así, `PageTop` podrá registrar todas estas acciones durante la inicialización de la extensión y
|
||||||
|
/// posteriormente despacharlas según corresponda.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! inject_actions {
|
||||||
|
() => {
|
||||||
|
Vec::<ActionBox>::new()
|
||||||
|
};
|
||||||
|
( $($action:expr),+ $(,)? ) => {{
|
||||||
|
vec![$(Box::new($action),)+]
|
||||||
|
}};
|
||||||
|
}
|
66
src/core/action/all.rs
Normal file
66
src/core/action/all.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
use crate::core::action::{ActionBox, ActionKey, ActionTrait, ActionsList};
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::{LazyLock, RwLock};
|
||||||
|
|
||||||
|
// ACCIONES ****************************************************************************************
|
||||||
|
|
||||||
|
static ACTIONS: LazyLock<RwLock<HashMap<ActionKey, ActionsList>>> =
|
||||||
|
LazyLock::new(|| RwLock::new(HashMap::new()));
|
||||||
|
|
||||||
|
// AÑADIR ACCIONES *********************************************************************************
|
||||||
|
|
||||||
|
// Registra una nueva acción en el sistema.
|
||||||
|
//
|
||||||
|
// Si ya existen acciones con la misma `ActionKey`, la acción se añade a la lista existente. Si no,
|
||||||
|
// se crea una nueva lista.
|
||||||
|
//
|
||||||
|
// # Uso típico
|
||||||
|
//
|
||||||
|
// Las extensiones llamarán a esta función durante su inicialización para instalar acciones
|
||||||
|
// personalizadas que modifiquen el comportamiento del núcleo o de otros componentes.
|
||||||
|
//
|
||||||
|
// ```rust,ignore
|
||||||
|
// add_action(Box::new(MyCustomAction::new()));
|
||||||
|
// ```
|
||||||
|
pub fn add_action(action: ActionBox) {
|
||||||
|
let key = action.key();
|
||||||
|
let mut actions = ACTIONS.write().unwrap();
|
||||||
|
if let Some(list) = actions.get_mut(&key) {
|
||||||
|
list.add(action);
|
||||||
|
} else {
|
||||||
|
let mut list = ActionsList::new();
|
||||||
|
list.add(action);
|
||||||
|
actions.insert(key, list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DESPLEGAR ACCIONES ******************************************************************************
|
||||||
|
|
||||||
|
/// Despacha las funciones asociadas a un [`ActionKey`] y las ejecuta.
|
||||||
|
///
|
||||||
|
/// Permite recorrer de forma segura y ordenada (por peso) la lista de funciones asociadas a una
|
||||||
|
/// acción específica.
|
||||||
|
///
|
||||||
|
/// # Parámetros genéricos
|
||||||
|
/// - `A`: Tipo de acción que esperamos procesar. Debe implementar [`ActionTrait`].
|
||||||
|
/// - `F`: Función o cierre que recibe cada acción y devuelve un valor de tipo `B`.
|
||||||
|
///
|
||||||
|
/// # Ejemplo de uso
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// dispatch_actions::<MyCustomAction, _>(&some_key, |action| {
|
||||||
|
/// action.do_something();
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Esto permite a PageTop o a otros módulos aplicar lógica específica a las acciones de un contexto
|
||||||
|
/// determinado, manteniendo la flexibilidad del sistema.
|
||||||
|
pub fn dispatch_actions<A, B, F>(key: &ActionKey, f: F)
|
||||||
|
where
|
||||||
|
A: ActionTrait,
|
||||||
|
F: FnMut(&A) -> B,
|
||||||
|
{
|
||||||
|
if let Some(list) = ACTIONS.read().unwrap().get(key) {
|
||||||
|
list.iter_map(f);
|
||||||
|
}
|
||||||
|
}
|
102
src/core/action/definition.rs
Normal file
102
src/core/action/definition.rs
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
use crate::core::AnyInfo;
|
||||||
|
use crate::{UniqueId, Weight};
|
||||||
|
|
||||||
|
/// Tipo dinámico para encapsular cualquier acción que implementa [`ActionTrait`].
|
||||||
|
pub type ActionBox = Box<dyn ActionTrait>;
|
||||||
|
|
||||||
|
/// Identifica una acción con una clave que define las condiciones de selección de las funciones
|
||||||
|
/// asociadas a esa acción.
|
||||||
|
///
|
||||||
|
/// Las funciones seleccionadas se van a [despachar](crate::core::action::dispatch_actions) y
|
||||||
|
/// ejecutar en un punto concreto del flujo de ejecución.
|
||||||
|
///
|
||||||
|
/// # Campos
|
||||||
|
///
|
||||||
|
/// - `action_type_id`: Tipo de la acción.
|
||||||
|
/// - `theme_type_id`: Opcional, filtra las funciones para un tema dado.
|
||||||
|
/// - `referer_type_id`: Opcional, filtra las funciones para un tipo dado (p.ej. para un tipo de
|
||||||
|
/// componente).
|
||||||
|
/// - `referer_id`: Opcional, filtra las funciones por el identificador de una instancia (p.ej. para
|
||||||
|
/// un formulario concreto).
|
||||||
|
#[derive(Eq, PartialEq, Hash)]
|
||||||
|
pub struct ActionKey {
|
||||||
|
action_type_id: UniqueId,
|
||||||
|
theme_type_id: Option<UniqueId>,
|
||||||
|
referer_type_id: Option<UniqueId>,
|
||||||
|
referer_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActionKey {
|
||||||
|
/// Crea una nueva clave para un tipo de acción.
|
||||||
|
///
|
||||||
|
/// Esta clave permite seleccionar las funciones a ejecutar para ese tipo de acción con filtros
|
||||||
|
/// opcionales por tema, un tipo de referencia, o una instancia concreta según su identificador.
|
||||||
|
pub fn new(
|
||||||
|
action_type_id: UniqueId,
|
||||||
|
theme_type_id: Option<UniqueId>,
|
||||||
|
referer_type_id: Option<UniqueId>,
|
||||||
|
referer_id: Option<String>,
|
||||||
|
) -> Self {
|
||||||
|
ActionKey {
|
||||||
|
action_type_id,
|
||||||
|
theme_type_id,
|
||||||
|
referer_type_id,
|
||||||
|
referer_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait base que permite obtener la clave ([`ActionKey`]) asociada a una acción.
|
||||||
|
///
|
||||||
|
/// Implementado automáticamente para cualquier tipo que cumpla [`ActionTrait`].
|
||||||
|
pub trait ActionBase {
|
||||||
|
fn key(&self) -> ActionKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interfaz común que deben implementar las acciones del código que pueden ser modificadas.
|
||||||
|
///
|
||||||
|
/// Este trait combina:
|
||||||
|
/// - [`AnyInfo`] para identificación única del tipo en tiempo de ejecución.
|
||||||
|
/// - `Send + Sync` para permitir uso concurrente seguro.
|
||||||
|
///
|
||||||
|
/// # Métodos personalizables
|
||||||
|
/// - `theme_type_id()`: Asocia la acción a un tipo concreto de tema (si aplica).
|
||||||
|
/// - `referer_type_id()`: Asocia la acción a un tipo de objeto referente (si aplica).
|
||||||
|
/// - `referer_id()`: Asocia la acción a un identificador concreto.
|
||||||
|
/// - `weight()`: Controla el orden de aplicación de acciones; valores más bajos se ejecutan antes.
|
||||||
|
pub trait ActionTrait: ActionBase + AnyInfo + Send + Sync {
|
||||||
|
/// Especifica el tipo de tema asociado. Por defecto `None`.
|
||||||
|
fn theme_type_id(&self) -> Option<UniqueId> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Especifica el tipo del objeto referente. Por defecto `None`.
|
||||||
|
fn referer_type_id(&self) -> Option<UniqueId> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Especifica un identificador único del objeto referente. Por defecto `None`.
|
||||||
|
fn referer_id(&self) -> Option<String> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Define el peso lógico de la acción para determinar el orden de aplicación.
|
||||||
|
///
|
||||||
|
/// Acciones con pesos más bajos se aplicarán antes. Se pueden usar valores negativos. Por
|
||||||
|
/// defecto es `0`.
|
||||||
|
fn weight(&self) -> Weight {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementación automática que construye la clave `ActionKey` a partir de los métodos definidos.
|
||||||
|
impl<A: ActionTrait> ActionBase for A {
|
||||||
|
fn key(&self) -> ActionKey {
|
||||||
|
ActionKey {
|
||||||
|
action_type_id: self.type_id(),
|
||||||
|
theme_type_id: self.theme_type_id(),
|
||||||
|
referer_type_id: self.referer_type_id(),
|
||||||
|
referer_id: self.referer_id(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
src/core/action/list.rs
Normal file
43
src/core/action/list.rs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
use crate::core::action::{ActionBox, ActionTrait};
|
||||||
|
use crate::core::AnyCast;
|
||||||
|
use crate::trace;
|
||||||
|
use crate::AutoDefault;
|
||||||
|
|
||||||
|
use std::sync::RwLock;
|
||||||
|
|
||||||
|
#[derive(AutoDefault)]
|
||||||
|
pub struct ActionsList(RwLock<Vec<ActionBox>>);
|
||||||
|
|
||||||
|
impl ActionsList {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
ActionsList::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(&mut self, action: ActionBox) {
|
||||||
|
let mut list = self.0.write().unwrap();
|
||||||
|
list.push(action);
|
||||||
|
list.sort_by_key(|a| a.weight());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter_map<A, B, F>(&self, mut f: F)
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
A: ActionTrait,
|
||||||
|
F: FnMut(&A) -> B,
|
||||||
|
{
|
||||||
|
let _: Vec<_> = self
|
||||||
|
.0
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.map(|a| {
|
||||||
|
if let Some(action) = (**a).downcast_ref::<A>() {
|
||||||
|
f(action);
|
||||||
|
} else {
|
||||||
|
trace::error!("Failed to downcast action of type {}", (**a).type_name());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::core::action::add_action;
|
||||||
use crate::core::extension::ExtensionRef;
|
use crate::core::extension::ExtensionRef;
|
||||||
use crate::core::theme::all::THEMES;
|
use crate::core::theme::all::THEMES;
|
||||||
use crate::{service, trace};
|
use crate::{service, trace};
|
||||||
|
@ -105,6 +106,16 @@ fn add_to_dropped(list: &mut Vec<ExtensionRef>, extension: ExtensionRef) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// REGISTRO DE LAS ACCIONES ************************************************************************
|
||||||
|
|
||||||
|
pub fn register_actions() {
|
||||||
|
for extension in ENABLED_EXTENSIONS.read().unwrap().iter() {
|
||||||
|
for a in extension.actions().into_iter() {
|
||||||
|
add_action(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// INICIALIZA LAS EXTENSIONES **********************************************************************
|
// INICIALIZA LAS EXTENSIONES **********************************************************************
|
||||||
|
|
||||||
pub fn initialize_extensions() {
|
pub fn initialize_extensions() {
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
use crate::core::action::ActionBox;
|
||||||
use crate::core::theme::ThemeRef;
|
use crate::core::theme::ThemeRef;
|
||||||
use crate::core::AnyInfo;
|
use crate::core::AnyInfo;
|
||||||
use crate::locale::L10n;
|
use crate::locale::L10n;
|
||||||
use crate::service;
|
use crate::{inject_actions, service};
|
||||||
|
|
||||||
/// Representa una referencia a una extensión.
|
/// Representa una referencia a una extensión.
|
||||||
///
|
///
|
||||||
|
@ -70,6 +71,15 @@ pub trait ExtensionTrait: AnyInfo + Send + Sync {
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Devuelve la lista de acciones que la extensión va a registrar.
|
||||||
|
///
|
||||||
|
/// Estas [acciones](crate::core::action) se despachan por orden de registro o por
|
||||||
|
/// [peso](crate::Weight), permitiendo personalizar el comportamiento de la aplicación en puntos
|
||||||
|
/// específicos.
|
||||||
|
fn actions(&self) -> Vec<ActionBox> {
|
||||||
|
inject_actions![]
|
||||||
|
}
|
||||||
|
|
||||||
/// Inicializa la extensión durante la lógica de arranque de la aplicación.
|
/// 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
|
/// Se llama una sola vez, después de que todas las dependencias se han inicializado y antes de
|
||||||
|
|
|
@ -65,6 +65,11 @@ impl Deref for StaticResources {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Identificador único de un tipo estático durante la ejecución de la aplicación.
|
||||||
|
///
|
||||||
|
/// **Nota:** El valor es único sólo dentro del proceso actual y cambia en cada compilación.
|
||||||
|
pub type UniqueId = std::any::TypeId;
|
||||||
|
|
||||||
/// Representa el peso lógico de una instancia en una colección ordenada por pesos.
|
/// Representa el peso lógico de una instancia en una colección ordenada por pesos.
|
||||||
///
|
///
|
||||||
/// Las instancias con pesos **más bajos**, incluyendo valores negativos (`-128..127`), se situarán
|
/// Las instancias con pesos **más bajos**, incluyendo valores negativos (`-128..127`), se situarán
|
||||||
|
@ -87,7 +92,7 @@ pub mod html;
|
||||||
pub mod locale;
|
pub mod locale;
|
||||||
// Soporte a fechas y horas.
|
// Soporte a fechas y horas.
|
||||||
pub mod datetime;
|
pub mod datetime;
|
||||||
// Tipos y funciones esenciales para crear extensiones y temas.
|
// Tipos y funciones esenciales para crear acciones, extensiones y temas.
|
||||||
pub mod core;
|
pub mod core;
|
||||||
// Gestión del servidor y servicios web.
|
// Gestión del servidor y servicios web.
|
||||||
pub mod service;
|
pub mod service;
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
pub use crate::{builder_fn, html, main, test};
|
pub use crate::{builder_fn, html, main, test};
|
||||||
|
|
||||||
pub use crate::{AutoDefault, StaticResources, Weight};
|
pub use crate::{AutoDefault, StaticResources, UniqueId, Weight};
|
||||||
|
|
||||||
// MACROS.
|
// MACROS.
|
||||||
|
|
||||||
|
@ -16,6 +16,8 @@ pub use crate::include_config;
|
||||||
pub use crate::include_locales;
|
pub use crate::include_locales;
|
||||||
// crate::service
|
// crate::service
|
||||||
pub use crate::{include_files, include_files_service};
|
pub use crate::{include_files, include_files_service};
|
||||||
|
// crate::core::action
|
||||||
|
pub use crate::inject_actions;
|
||||||
|
|
||||||
// API.
|
// API.
|
||||||
|
|
||||||
|
@ -35,6 +37,7 @@ pub use crate::service;
|
||||||
|
|
||||||
pub use crate::core::{AnyCast, AnyInfo, TypeInfo};
|
pub use crate::core::{AnyCast, AnyInfo, TypeInfo};
|
||||||
|
|
||||||
|
pub use crate::core::action::*;
|
||||||
pub use crate::core::extension::*;
|
pub use crate::core::extension::*;
|
||||||
pub use crate::core::theme::*;
|
pub use crate::core::theme::*;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue