🎨 Convierte el ciclo de renderizado en async
`prepare()` de `Component`, `render()` de `Region` y `Template`, y la implementación de `ComponentRender` pasan a ser funciones async. Se actualizan todos los componentes base, helpers, tests y ejemplos.
This commit is contained in:
parent
9acd8cc51a
commit
9354894b3a
48 changed files with 436 additions and 365 deletions
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
mod error;
|
||||
pub use error::ErrorPage;
|
||||
pub(crate) use error::render_error_pages;
|
||||
|
||||
use crate::auth::CurrentUser;
|
||||
use crate::base::action;
|
||||
|
|
@ -200,7 +201,7 @@ impl Page {
|
|||
/// `lang` y `dir` en la etiqueta `<html>`.
|
||||
/// 8. Compone el documento HTML completo (`<!DOCTYPE html>`, `<html>`, `<head>`, `<body>`) y
|
||||
/// devuelve un [`Result`] con el [`Markup`] final.
|
||||
pub fn render(&mut self) -> Result<Markup, ErrorPage> {
|
||||
pub async fn render(&mut self) -> Result<Markup, ErrorPage> {
|
||||
// Acciones específicas del tema antes de renderizar el <body>.
|
||||
self.context.theme().before_render_page_body(self);
|
||||
|
||||
|
|
@ -209,9 +210,9 @@ impl Page {
|
|||
|
||||
// Renderiza el <body>.
|
||||
let body = html! {
|
||||
(ReservedRegion::PageTop.render(&mut self.context))
|
||||
(self.context.theme().render_page_body(self))
|
||||
(ReservedRegion::PageBottom.render(&mut self.context))
|
||||
(ReservedRegion::PageTop.render(&mut self.context).await)
|
||||
(self.context.theme().render_page_body(self).await)
|
||||
(ReservedRegion::PageBottom.render(&mut self.context).await)
|
||||
};
|
||||
|
||||
// Acciones específicas del tema después de renderizar el <body>.
|
||||
|
|
@ -221,7 +222,7 @@ impl Page {
|
|||
action::page::AfterRenderBody::dispatch(self);
|
||||
|
||||
// Renderiza el <head>.
|
||||
let head = self.context.theme().render_page_head(self);
|
||||
let head = self.context.theme().render_page_head(self).await;
|
||||
|
||||
// Compone la página incluyendo los atributos de idioma y dirección del texto.
|
||||
let lang = &self.context.langid().language;
|
||||
|
|
@ -283,7 +284,7 @@ impl Contextual for Page {
|
|||
}
|
||||
|
||||
#[builder_fn]
|
||||
fn with_param<T: 'static>(mut self, key: &'static str, value: T) -> Self {
|
||||
fn with_param<T: Send + Sync + 'static>(mut self, key: &'static str, value: T) -> Self {
|
||||
self.context.alter_param(key, value);
|
||||
self
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
use axum::extract::Request;
|
||||
use axum::middleware::Next;
|
||||
|
||||
use crate::core::component::Contextual;
|
||||
use crate::locale::L10n;
|
||||
use crate::util;
|
||||
|
|
@ -5,20 +8,18 @@ use crate::web::{HttpRequest, IntoResponse, Response, http};
|
|||
|
||||
use super::Page;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
/// Página de error asociada a un código de estado HTTP.
|
||||
///
|
||||
/// Este enumerado agrupa tipos esenciales de error que pueden devolverse como página HTML completa.
|
||||
/// Cada variante encapsula la solicitud original ([`HttpRequest`]) y se corresponde con un código
|
||||
/// de estado concreto.
|
||||
///
|
||||
/// Para cada error se construye una [`Page`] usando el tema activo, lo que permite personalizar
|
||||
/// la plantilla y el contenido del mensaje mediante los métodos específicos del tema
|
||||
/// (por ejemplo, [`Theme::error_403()`](crate::core::theme::Theme::error_403),
|
||||
/// Para cada error se construye una [`Page`] usando el tema activo, lo que permite personalizar la
|
||||
/// plantilla y el contenido del mensaje mediante los métodos específicos del tema (por ejemplo,
|
||||
/// [`Theme::error_403()`](crate::core::theme::Theme::error_403),
|
||||
/// [`Theme::error_404()`](crate::core::theme::Theme::error_404) o
|
||||
/// [`Theme::error_fatal()`](crate::core::theme::Theme::error_fatal)).
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ErrorPage {
|
||||
BadRequest(HttpRequest),
|
||||
AccessDenied(HttpRequest),
|
||||
|
|
@ -29,26 +30,6 @@ pub enum ErrorPage {
|
|||
}
|
||||
|
||||
impl ErrorPage {
|
||||
// Renderiza una página de error genérica usando el tema activo. Deriva las claves de
|
||||
// localización del código de estado (`error<code>_title`, `_alert`, `_help`). Si el
|
||||
// renderizado falla, escribe el texto plano del código de estado.
|
||||
fn display_error_page(&self, f: &mut fmt::Formatter<'_>, request: &HttpRequest) -> fmt::Result {
|
||||
let mut page = Page::new(request.clone());
|
||||
let code = self.status_code();
|
||||
page.theme().error_fatal(
|
||||
&mut page,
|
||||
code,
|
||||
L10n::l(util::join!("error", code.as_str(), "_title")),
|
||||
L10n::l(util::join!("error", code.as_str(), "_alert")),
|
||||
L10n::l(util::join!("error", code.as_str(), "_help")),
|
||||
);
|
||||
if let Ok(rendered) = page.render() {
|
||||
write!(f, "{}", rendered.into_string())
|
||||
} else {
|
||||
f.write_str(code.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
/// Devuelve el código de estado HTTP asociado a la variante de error.
|
||||
pub fn status_code(&self) -> http::StatusCode {
|
||||
match self {
|
||||
|
|
@ -60,59 +41,68 @@ impl ErrorPage {
|
|||
ErrorPage::GatewayTimeout(_) => http::StatusCode::GATEWAY_TIMEOUT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ErrorPage {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
// Error 400.
|
||||
Self::BadRequest(request) => self.display_error_page(f, request),
|
||||
|
||||
// Error 403.
|
||||
// Renderiza la página de error y construye la respuesta HTTP completa con el HTML generado. Si
|
||||
// el renderizado falla, devuelve sólo el código de estado sin cuerpo.
|
||||
async fn render_html(self) -> Response {
|
||||
let status = self.status_code();
|
||||
let mut page = match self {
|
||||
Self::AccessDenied(request) => {
|
||||
let mut page = Page::new(request.clone());
|
||||
let mut page = Page::new(request);
|
||||
page.theme().error_403(&mut page);
|
||||
if let Ok(rendered) = page.render() {
|
||||
write!(f, "{}", rendered.into_string())
|
||||
} else {
|
||||
f.write_str(self.status_code().as_str())
|
||||
}
|
||||
page
|
||||
}
|
||||
|
||||
// Error 404.
|
||||
Self::NotFound(request) => {
|
||||
let mut page = Page::new(request.clone());
|
||||
let mut page = Page::new(request);
|
||||
page.theme().error_404(&mut page);
|
||||
if let Ok(rendered) = page.render() {
|
||||
write!(f, "{}", rendered.into_string())
|
||||
} else {
|
||||
f.write_str(self.status_code().as_str())
|
||||
}
|
||||
page
|
||||
}
|
||||
|
||||
// Error 500.
|
||||
Self::InternalError(request) => self.display_error_page(f, request),
|
||||
|
||||
// Error 503.
|
||||
Self::ServiceUnavailable(request) => self.display_error_page(f, request),
|
||||
|
||||
// Error 504.
|
||||
Self::GatewayTimeout(request) => self.display_error_page(f, request),
|
||||
Self::BadRequest(request)
|
||||
| Self::InternalError(request)
|
||||
| Self::ServiceUnavailable(request)
|
||||
| Self::GatewayTimeout(request) => {
|
||||
let mut page = Page::new(request);
|
||||
page.theme().error_fatal(
|
||||
&mut page,
|
||||
status,
|
||||
L10n::l(util::join!("error", status.as_str(), "_title")),
|
||||
L10n::l(util::join!("error", status.as_str(), "_alert")),
|
||||
L10n::l(util::join!("error", status.as_str(), "_help")),
|
||||
);
|
||||
page
|
||||
}
|
||||
};
|
||||
match page.render().await {
|
||||
Ok(rendered) => (
|
||||
status,
|
||||
[(http::header::CONTENT_TYPE, "text/html; charset=utf-8")],
|
||||
rendered.into_string(),
|
||||
)
|
||||
.into_response(),
|
||||
Err(_) => status.into_response(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convierte un [`ErrorPage`] en una respuesta HTTP con el código de estado adecuado y el cuerpo
|
||||
/// HTML generado por el tema activo.
|
||||
/// Convierte un [`ErrorPage`] en una respuesta HTTP que luego se convertirá en una página HTML
|
||||
/// completa usando el tema activo.
|
||||
impl IntoResponse for ErrorPage {
|
||||
fn into_response(self) -> Response {
|
||||
let status = self.status_code();
|
||||
let body = self.to_string();
|
||||
(
|
||||
status,
|
||||
[(http::header::CONTENT_TYPE, "text/html; charset=utf-8")],
|
||||
body,
|
||||
)
|
||||
.into_response()
|
||||
let mut response = status.into_response();
|
||||
response.extensions_mut().insert(self);
|
||||
response
|
||||
}
|
||||
}
|
||||
|
||||
// Intercepta respuestas con un [`ErrorPage`] pendiente y las convierte en páginas HTML completas
|
||||
// usando el tema activo.
|
||||
//
|
||||
// Se registra globalmente sobre el router principal desde [`crate::app`].
|
||||
pub(crate) async fn render_error_pages(req: Request, next: Next) -> Response {
|
||||
let mut response = next.run(req).await;
|
||||
if let Some(error_page) = response.extensions_mut().remove::<ErrorPage>() {
|
||||
return error_page.render_html().await;
|
||||
}
|
||||
response
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue