(auth): Añade CurrentUser y sistema de permisos

- Nuevo módulo `auth` que exporta `CurrentUser`, `CheckPermission` y
  `has_permission()`.
- `Context` y `Page` exponen `current_user()` vía `Contextual`.
- `HttpRequest` accede a extensiones de middleware con `extension<T>()`.
- `Extension` incluye nuevo método `configure_middleware`.
- `try_dispatch_actions` para despachar acciones con control del flujo.
This commit is contained in:
Manuel Cillero 2026-07-02 20:52:13 +02:00
parent 3120fd9a6f
commit 0e96bcce64
22 changed files with 544 additions and 84 deletions

View file

@ -45,9 +45,9 @@ pub(crate) fn add_action(action: ActionBox) {
/// # Parámetros genéricos
///
/// - `A`: Tipo de acción que esperamos procesar. Debe implementar [`ActionDispatcher`].
/// - `F`: Función asociada a cada acción, devuelve un valor de tipo `B`.
/// - `F`: Función que se aplica para una acción dada.
///
/// # Ejemplo de uso
/// # Ejemplo
///
/// ```rust,ignore
/// pub(crate) fn dispatch(component: &mut C, cx: &mut Context) {
@ -61,12 +61,47 @@ pub(crate) fn add_action(action: ActionBox) {
/// );
/// }
/// ```
pub fn dispatch_actions<A, B, F>(key: &ActionKey, f: F)
pub fn dispatch_actions<A, F>(key: &ActionKey, f: F)
where
A: ActionDispatcher,
F: FnMut(&A) -> B,
F: FnMut(&A),
{
if let Some(list) = ACTIONS.read().get(key) {
list.iter_map(f);
list.for_each(f);
}
}
/// Despacha las funciones asociadas a una [`ActionKey`] con posible salida anticipada.
///
/// Funciona igual que [`dispatch_actions`], pero el *closure* puede devolver
/// [`std::ops::ControlFlow::Continue`] para continuar ejecutando la siguiente acción; o
/// [`std::ops::ControlFlow::Break`] para detener la iteración inmediatamente.
///
/// # Ejemplo
///
/// ```rust,ignore
/// pub(crate) fn check(cx: &Context, key: &str) -> bool {
/// let mut granted = false;
/// try_dispatch_actions(
/// &ActionKey::new(UniqueId::of::<Self>(), None, None),
/// |action: &Self| {
/// (action.f)(cx, key, &mut granted);
/// if granted {
/// std::ops::ControlFlow::Break(())
/// } else {
/// std::ops::ControlFlow::Continue(())
/// }
/// },
/// );
/// granted
/// }
/// ```
pub fn try_dispatch_actions<A, F>(key: &ActionKey, f: F)
where
A: ActionDispatcher,
F: FnMut(&A) -> std::ops::ControlFlow<()>,
{
if let Some(list) = ACTIONS.read().get(key) {
list.try_for_each(f);
}
}

View file

@ -45,17 +45,17 @@ impl ActionKey {
/// Las acciones tienen que sobrescribir los métodos para el filtro que apliquen. Por defecto
/// implementa un filtro nulo.
pub trait ActionDispatcher: AnyInfo + Send + Sync {
/// Identificador de tipo ([`UniqueId`]) del objeto referido. En este caso devuelve `None`.
/// Devuelve el identificador de tipo ([`UniqueId`]) del objeto referido.
fn referer_type_id(&self) -> Option<UniqueId> {
None
}
/// Identificador del objeto referido. En este caso devuelve `None`.
/// Devuelve el identificador del objeto referido.
fn referer_id(&self) -> Option<String> {
None
}
/// Funciones con pesos más bajos se aplican antes. En este caso siempre devuelve `0`.
/// Devuelve el peso para definir el orden de ejecución.
fn weight(&self) -> Weight {
0
}

View file

@ -19,24 +19,35 @@ impl ActionsList {
list.sort_by_key(|a| a.weight());
}
pub fn iter_map<A, B, F>(&self, mut f: F)
pub fn for_each<A, F>(&self, mut f: F)
where
Self: Sized,
A: ActionDispatcher,
F: FnMut(&A) -> B,
F: FnMut(&A),
{
let _: Vec<_> = self
.0
.read()
.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());
let list = self.0.read();
for a in list.iter().rev() {
if let Some(action) = (**a).downcast_ref::<A>() {
f(action);
} else {
trace::error!("Failed to downcast action of type {}", (**a).type_name());
}
}
}
pub fn try_for_each<A, F>(&self, mut f: F)
where
A: ActionDispatcher,
F: FnMut(&A) -> std::ops::ControlFlow<()>,
{
let list = self.0.read();
for a in list.iter().rev() {
if let Some(action) = (**a).downcast_ref::<A>() {
if f(action).is_break() {
break;
}
})
.collect();
} else {
trace::error!("Failed to downcast action of type {}", (**a).type_name());
}
}
}
}