Simplifica el nombre del tipo de localización. Actualiza todas las referencias en el crate principal, extensiones, ejemplos y tests.
278 lines
9.8 KiB
Rust
278 lines
9.8 KiB
Rust
use crate::actions;
|
|
use crate::async_trait;
|
|
use crate::core::AnyInfo;
|
|
use crate::core::action::ActionBox;
|
|
use crate::core::theme::ThemeRef;
|
|
use crate::locale::Lc;
|
|
use crate::web::Router;
|
|
|
|
/// Interfaz común que debe implementar cualquier extensión de PageTop.
|
|
///
|
|
/// Este *trait* es fácil de implementar, basta con declarar una estructura sin campos para la
|
|
/// extensión y sobrescribir los métodos que sean necesarios. Por ejemplo:
|
|
///
|
|
/// ```rust,no_run
|
|
/// # use pagetop::prelude::*;
|
|
/// pub struct MyExtension;
|
|
///
|
|
/// #[async_trait]
|
|
/// impl Extension for MyExtension {
|
|
/// fn name(&self) -> Lc {
|
|
/// Lc::n("My Extension")
|
|
/// }
|
|
///
|
|
/// fn description(&self) -> Lc {
|
|
/// Lc::n("Does something useful")
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
#[async_trait]
|
|
pub trait Extension: AnyInfo + Send + Sync {
|
|
/// Nombre de la extensión como *texto localizado* legible para el usuario.
|
|
///
|
|
/// Predeterminado por el [`short_name()`](AnyInfo::short_name) del tipo asociado a la
|
|
/// extensión.
|
|
fn name(&self) -> Lc {
|
|
Lc::n(self.short_name())
|
|
}
|
|
|
|
/// Descripción corta de la extensión como *texto localizado* para paneles, listados, etc.
|
|
///
|
|
/// Por defecto devuelve un valor vacío (`Lc::default()`).
|
|
fn description(&self) -> Lc {
|
|
Lc::default()
|
|
}
|
|
|
|
/// Devuelve una referencia a esta misma extensión cuando actúa como un tema.
|
|
///
|
|
/// Para ello, la implementación concreta debe ser una extensión que también implemente
|
|
/// [`Theme`](crate::core::theme::Theme). Por defecto, asume que la extensión no es un tema y
|
|
/// devuelve `None`.
|
|
///
|
|
/// # Ejemplo
|
|
///
|
|
/// ```rust,no_run
|
|
/// # use pagetop::prelude::*;
|
|
/// pub struct MyTheme;
|
|
///
|
|
/// #[async_trait]
|
|
/// impl Extension for MyTheme {
|
|
/// fn theme(&self) -> Option<ThemeRef> {
|
|
/// Some(&Self)
|
|
/// }
|
|
/// }
|
|
///
|
|
/// #[async_trait]
|
|
/// impl Theme for MyTheme {}
|
|
/// ```
|
|
fn theme(&self) -> Option<ThemeRef> {
|
|
None
|
|
}
|
|
|
|
/// Extensiones que deben inicializarse **antes** de esta.
|
|
///
|
|
/// PageTop usa este método para construir un grafo de dependencias en tiempo de ejecución entre
|
|
/// extensiones. Garantiza que todas las extensiones requeridas están presentes y se inicializan
|
|
/// en el orden apropiado, es decir, las dependencias siempre antes que la extensión que las
|
|
/// declara.
|
|
///
|
|
/// Esta declaración es independiente de las dependencias en `Cargo.toml`, donde Rust gestiona
|
|
/// las dependencias de los *crates* en tiempo de compilación.
|
|
///
|
|
/// # Ejemplo
|
|
///
|
|
/// ```rust,no_run
|
|
/// # use pagetop::prelude::*;
|
|
/// # pub struct Database;
|
|
/// # #[async_trait]
|
|
/// # impl Extension for Database {}
|
|
/// pub struct MyApp;
|
|
///
|
|
/// #[async_trait]
|
|
/// impl Extension for MyApp {
|
|
/// fn dependencies(&self) -> Vec<ExtensionRef> {
|
|
/// vec![&Database]
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
fn dependencies(&self) -> Vec<ExtensionRef> {
|
|
vec![]
|
|
}
|
|
|
|
/// Devuelve la lista de acciones que la extensión registra.
|
|
///
|
|
/// Estas [acciones](crate::core::action) se despachan por orden de registro o por
|
|
/// [peso](crate::Weight) (ver [`actions!`](crate::actions)), permitiendo
|
|
/// personalizar el comportamiento de la aplicación en puntos específicos.
|
|
fn actions(&self) -> Vec<ActionBox> {
|
|
actions![]
|
|
}
|
|
|
|
/// Inicializa la extensión durante la fase de arranque de la aplicación.
|
|
///
|
|
/// La firma extendida que se muestra es el resultado de aplicar la macro
|
|
/// [`pagetop::async_trait`](pagetop::async_trait) a la declaración del método:
|
|
///
|
|
/// ```text
|
|
/// async fn initialize(&self) {}
|
|
/// ```
|
|
///
|
|
/// Solo es necesario sobrescribir este método **cuando la extensión necesita ejecutar alguna
|
|
/// lógica de inicialización**. PageTop lo invoca una sola vez, después de que todas las
|
|
/// dependencias se han inicializado y antes de aceptar cualquier petición HTTP.
|
|
///
|
|
/// ```rust,no_run
|
|
/// use pagetop::prelude::*;
|
|
///
|
|
/// pub struct MyExtension;
|
|
///
|
|
/// #[async_trait]
|
|
/// impl Extension for MyExtension {
|
|
/// async fn initialize(&self) {
|
|
/// // lógica de inicialización...
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
async fn initialize(&self) {}
|
|
|
|
/// Registra rutas, servicios y capas de la extensión en el servidor web de la aplicación.
|
|
///
|
|
/// 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.
|
|
///
|
|
/// # 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, [assets] => "/path")` |
|
|
/// | Estado compartido entre handlers | `.with_state(my_state)` |
|
|
///
|
|
/// # Ejemplos
|
|
///
|
|
/// ## Rutas HTTP básicas
|
|
///
|
|
/// ```rust,no_run
|
|
/// # use pagetop::prelude::*;
|
|
/// # async fn list_posts() -> &'static str { "" }
|
|
/// # async fn view_post() -> &'static str { "" }
|
|
/// # async fn create_post() -> &'static str { "" }
|
|
/// pub struct Blog;
|
|
///
|
|
/// #[async_trait]
|
|
/// 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))
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// ## Rutas agrupadas bajo un prefijo
|
|
///
|
|
/// ```rust,no_run
|
|
/// # use pagetop::prelude::*;
|
|
/// # async fn dashboard() -> &'static str { "" }
|
|
/// # async fn list_users() -> &'static str { "" }
|
|
/// pub struct Admin;
|
|
///
|
|
/// #[async_trait]
|
|
/// 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 middleware acotado a esta extensión
|
|
///
|
|
/// Cada `Router` mantiene su propia pila de middleware independiente. Cuando se crea un
|
|
/// `Router::new()` separado y se llama a `.layer()` sobre él, esa capa sólo se aplica a las
|
|
/// rutas de ese router concreto. Al fusionarlo con `.merge()` en el `router` principal, cada
|
|
/// ruta se añade con su middleware asociado, sin tocar las demás.
|
|
///
|
|
/// Otra cosa es llamar a `.layer()` directamente sobre el `router` principal que se recibe como
|
|
/// parámetro, porque ese objeto ya contiene todas las rutas acumuladas por extensiones
|
|
/// anteriores, y la capa se aplica a todas las rutas, no sólo a las propias.
|
|
///
|
|
/// ```rust,ignore
|
|
/// # use pagetop::prelude::*;
|
|
/// pub struct Api;
|
|
///
|
|
/// #[async_trait]
|
|
/// impl Extension for Api {
|
|
/// fn configure_router(&self, router: Router) -> Router {
|
|
/// let api = Router::new()
|
|
/// .route("/api/data", web::get(get_data))
|
|
/// .layer(auth_layer());
|
|
/// router.merge(api)
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// Para middleware que deba cubrir **todas** las rutas, usar
|
|
/// [`configure_middleware`](Self::configure_middleware).
|
|
///
|
|
/// ## 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;
|
|
///
|
|
/// #[async_trait]
|
|
/// 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
|
|
}
|
|
|
|
/// Añade capas de middleware globales al router ya completamente preparado.
|
|
///
|
|
/// Se invoca **después** de que todas las extensiones hayan registrado sus rutas con
|
|
/// [`configure_router`](Self::configure_router), de modo que las capas añadidas aquí se aplican
|
|
/// a **todas** las rutas de la aplicación, independientemente del orden de las extensiones.
|
|
///
|
|
/// Usar este método cuando el middleware deba interceptar cualquier petición entrante (p. ej.
|
|
/// resolución de sesión, autenticación, cabeceras de seguridad, ...).
|
|
///
|
|
/// # Ejemplo
|
|
///
|
|
/// ```rust,no_run
|
|
/// # use pagetop::prelude::*;
|
|
/// # use axum::middleware;
|
|
/// # async fn session_middleware(
|
|
/// # req: axum::extract::Request,
|
|
/// # next: middleware::Next,
|
|
/// # ) -> axum::response::Response { next.run(req).await }
|
|
/// pub struct MyAuth;
|
|
///
|
|
/// #[async_trait]
|
|
/// impl Extension for MyAuth {
|
|
/// fn configure_middleware(&self, router: Router) -> Router {
|
|
/// router.layer(middleware::from_fn(session_middleware))
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
fn configure_middleware(&self, router: Router) -> Router {
|
|
router
|
|
}
|
|
}
|
|
|
|
/// Representa una referencia a una extensión.
|
|
pub type ExtensionRef = &'static dyn Extension;
|