✨ (core): Añade Route, RoutePath y Waypoint

`Route` resuelve URLs sensibles al `Context`/idioma en propiedades de
componentes; `RoutePath` (renombrado desde html/route.rs) es el valor
concreto de ruta + query. `Waypoint` transporta la URL de retorno entre
pantallas de listado/alta/edición vía query string.

Ajusta `Context::route()`, `Redirect` y los componentes que aceptan
rutas para integrarse con el nuevo tipo `Route`.
This commit is contained in:
Manuel Cillero 2026-07-19 22:16:21 +02:00
parent 6f82da220b
commit af548b03c9
25 changed files with 819 additions and 216 deletions

View file

@ -1,6 +1,10 @@
//! API para construir nuevos componentes.
use crate::html::RoutePath;
//!
//! Para cualquier `href`, `action` o redirección que deba preservar el idioma negociado (anexando a
//! la URL el parámetro de consulta `?lang=...`), PageTop no usa `String`/`&str` sueltos, sino que
//! usa [`Context::route()`] cuando ya se tiene el contexto de renderizado a mano, o [`Route`] para
//! campos de componente que se construyen una vez y se resuelven más tarde, en cada petición. La
//! documentación de [`Route`] explica el criterio completo para elegir entre ambos.
mod error;
pub use error::ComponentError;
@ -18,6 +22,9 @@ pub use message::{MessageLevel, StatusMessage};
mod context;
pub use context::{AssetsOp, Context, ContextError, Contextual};
mod route;
pub use route::Route;
/// Alias de función (*callback*) para **determinar si un componente se renderiza o no**.
///
/// Puede usarse para permitir que una instancia concreta de un tipo de componente dado decida
@ -71,41 +78,3 @@ pub use context::{AssetsOp, Context, ContextError, Contextual};
/// }
/// ```
pub type FnIsRenderable = fn(cx: &Context) -> bool;
/// Alias de función (*callback*) para **resolver una ruta URL** según el contexto de renderizado.
///
/// Se usa para generar enlaces dinámicos en función del contexto (petición, idioma, parámetros,
/// etc.). Devuelve una [`RoutePath`], que representa un *path* base junto con una lista opcional de
/// parámetros de consulta.
///
/// El caso más común es construir rutas relativas dependientes del contexto, normalmente usando
/// [`Context::route`](crate::core::component::Context::route):
///
/// ```rust,no_run
/// # use pagetop::prelude::*;
/// # let relative_route: FnPathByContext =
/// |cx| cx.route("/path/to/page")
/// # ;
/// ```
///
/// También es posible usar rutas estáticas sin asignaciones adicionales:
///
/// ```rust,no_run
/// # use pagetop::prelude::*;
/// # let external_route: FnPathByContext =
/// |_| "https://www.example.com".into()
/// # ;
/// ```
///
/// O componer rutas dinámicas en tiempo de ejecución:
///
/// ```rust,no_run
/// # use pagetop::prelude::*;
/// # let dynamic_route: FnPathByContext =
/// |cx| RoutePath::new("/user").with_param("id", cx.param::<u64>("user_id").unwrap().to_string())
/// # ;
/// ```
///
/// Los componentes que acepten un [`FnPathByContext`] invocarán esta función durante el renderizado
/// para obtener la URL final que se asignará al atributo HTML correspondiente.
pub type FnPathByContext = fn(cx: &Context) -> RoutePath;

View file

@ -8,13 +8,13 @@ use crate::html::{Markup, Props, PropsOp, RoutePath, html};
use crate::locale::L10n;
use crate::locale::{LangId, LanguageIdentifier, RequestLocale};
use crate::web::HttpRequest;
use crate::{CowStr, builder_fn, util};
use crate::{builder_fn, util};
use parking_lot::Mutex;
use thiserror::Error;
use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::fmt;
/// Operaciones para modificar recursos asociados al [`Context`] de un documento.
pub enum AssetsOp {
@ -40,14 +40,15 @@ pub enum AssetsOp {
}
/// Errores de acceso a parámetros dinámicos del contexto.
///
/// - [`ContextError::ParamNotFound`]: la clave no existe.
/// - [`ContextError::ParamTypeMismatch`]: la clave existe, pero el valor guardado no coincide con
/// el tipo solicitado. Incluye nombre de la clave (`key`), tipo esperado (`expected`) y tipo
/// realmente guardado (`saved`) para facilitar el diagnóstico.
#[derive(Debug)]
#[derive(Debug, Error)]
pub enum ContextError {
/// La clave no existe.
#[error("parameter not found")]
ParamNotFound,
/// La clave existe, pero el valor guardado no coincide con el tipo solicitado. Incluye
/// nombre de la clave (`key`), tipo esperado (`expected`) y tipo realmente guardado (`saved`)
/// para facilitar el diagnóstico.
#[error("type mismatch for parameter \"{key}\": expected \"{expected}\", found \"{saved}\"")]
ParamTypeMismatch {
key: &'static str,
expected: &'static str,
@ -55,26 +56,6 @@ pub enum ContextError {
},
}
impl fmt::Display for ContextError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ContextError::ParamNotFound => {
write!(f, "parameter not found")
}
ContextError::ParamTypeMismatch {
key,
expected,
saved,
} => write!(
f,
"type mismatch for parameter \"{key}\": expected \"{expected}\", found \"{saved}\""
),
}
}
}
impl std::error::Error for ContextError {}
/// Interfaz para gestionar el **contexto de renderizado** de un documento HTML.
///
/// `Contextual` extiende [`LangId`] para establecer el idioma del documento y añade métodos para:
@ -86,7 +67,7 @@ impl std::error::Error for ContextError {}
/// - Leer y mantener **parámetros dinámicos tipados** de contexto.
///
/// Lo implementan, típicamente, estructuras que manejan el contexto de renderizado, como
/// [`Context`](crate::core::component::Context) o [`Page`](crate::response::page::Page).
/// [`Context`](crate::core::component::Context) o [`Page`](crate::response::Page).
///
/// # Ejemplo
///
@ -255,12 +236,51 @@ pub trait Contextual: LangId {
fn remove_param(&mut self, key: &'static str) -> bool;
}
// Cómo obtener la plantilla activa del contexto: la del tema (por defecto o de administración), o
// una fijada explícitamente. Se resuelve contra el tema activo al leer `Context::template()`, no al
// asignarla, para que un cambio de tema posterior con `with_theme()` se refleje automáticamente.
enum TemplateSource {
// Plantilla por defecto.
Default,
// Plantilla de administración del tema activo.
Admin,
// Plantilla fijada explícitamente con `with_template()`.
Explicit(TemplateRef),
}
impl TemplateSource {
fn resolve(&self, theme: ThemeRef) -> TemplateRef {
match self {
Self::Default => theme.default_template(),
Self::Admin => theme.admin_template(),
Self::Explicit(template) => *template,
}
}
}
/// Implementa un **contexto de renderizado** para un documento HTML.
///
/// Extiende [`Contextual`] con métodos para **instanciar** y configurar un nuevo contexto,
/// **renderizar los recursos** del documento (incluyendo el [`Favicon`], las hojas de estilo
/// [`StyleSheet`] y los scripts [`JavaScript`]), o extender el uso de **parámetros dinámicos
/// tipados** con nuevos métodos.
/// Se crea una sola vez por petición usando [`Context::new()`] (típicamente a través de
/// [`Page::new()`](crate::response::Page::new) o [`Page::admin()`](crate::response::Page::admin)),
/// y es la única vía por la que un componente, una acción o el tema activo conocen: la petición
/// HTTP de origen, el idioma negociado, el usuario autenticado
/// ([`current_user()`](Contextual::current_user)), el tema y la plantilla en uso, y los recursos
/// (favicon, hojas de estilo, scripts) acumulados hasta ese momento. Otros datos que los
/// componentes necesiten durante el renderizado pueden ser parámetros dinámicos tipados con
/// [`with_param()`](Contextual::with_param)/[`param()`](Contextual::param).
///
/// La implementación extiende [`Contextual`], que aporta los métodos *builder* (`with_*`) y los
/// *getters* comunes a cualquier estructura que gestione un contexto de renderizado (también los
/// implementa [`Page`](crate::response::Page)). Además, `Context` añade:
///
/// - [`route()`](Self::route) para construir URLs que preserven `?lang=...` cuando corresponda.
/// - [`build_id()`](Self::build_id)/[`required_id()`](Self::required_id) para generar
/// identificadores HTML únicos por tipo de componente.
/// - [`push_message()`](Self::push_message)/[`messages()`](Self::messages) para acumular
/// [`StatusMessage`] que mostrar en algún momento del renderizado.
/// - [`render_assets()`](Self::render_assets)/[`render_region_named()`](Self::render_region_named),
/// usados internamente por [`Page`](crate::response::Page) para producir el HTML final del
/// documento.
///
/// # Ejemplos
///
@ -306,29 +326,6 @@ pub trait Contextual: LangId {
/// let _unique_id = cx.build_id::<Menu>(1); // => "menu-1" si es el primero
/// }
/// ```
// Cómo obtener la plantilla activa del contexto: la del tema (por defecto o de administración), o
// una fijada explícitamente. Se resuelve contra el tema activo al leer `Context::template()`, no al
// asignarla, para que un cambio de tema posterior con `with_theme()` se refleje automáticamente.
enum TemplateSource {
// Plantilla por defecto.
Default,
// Plantilla de administración del tema activo.
Admin,
// Plantilla fijada explícitamente con `with_template()`.
Explicit(TemplateRef),
}
impl TemplateSource {
fn resolve(&self, theme: ThemeRef) -> TemplateRef {
match self {
Self::Default => theme.default_template(),
Self::Admin => theme.admin_template(),
Self::Explicit(template) => *template,
}
}
}
#[rustfmt::skip]
pub struct Context {
request : Option<HttpRequest>, // Petición HTTP de origen.
@ -440,17 +437,24 @@ impl Context {
/// Construye una ruta aplicada al contexto actual.
///
/// La ruta resultante se envuelve en un [`RoutePath`], que permite añadir parámetros de
/// consulta de forma tipada. Si la política de negociación de idioma actual
/// [`LangNegotiation`](crate::global::LangNegotiation) indica que debe propagarse el idioma
/// para esta petición, se añade o actualiza el parámetro de *query* `lang=...` con el
/// identificador de idioma efectivo del contexto.
/// Acepta cualquier tipo convertible a [`RoutePath`] (un literal, un `String`, un `&str` de
/// cualquier vida, o un [`RoutePath`] ya construido con sus propios parámetros). Si la política
/// de negociación del idioma ([`LangNegotiation`](crate::global::LangNegotiation)) indica que
/// debe propagarse el idioma para esta petición, se añade o actualiza automáticamente el
/// parámetro de *query* `lang=...` con el identificador de idioma definido en el contexto.
///
/// Esto garantiza que los enlaces generados desde el contexto preservan la preferencia de
/// idioma del usuario cuando procede.
pub fn route(&self, path: impl Into<CowStr>) -> RoutePath {
let mut route = RoutePath::new(path);
if self.locale.needs_lang_query() {
/// idioma del usuario durante la navegación. Si `path` **parece** una URL externa (ver
/// [`util::url_looks_external()`](crate::util::url_looks_external)), nunca se le añade `lang`.
///
/// Este método asume que ya tienes `cx` a mano en el momento de construir la ruta (dentro de
/// `prepare()`, un *handler* HTTP, etc.). Si lo que estás definiendo es un campo de componente
/// que se construye una sola vez y se reutiliza en peticiones futuras (un menú, un botón,
/// etc.), usa [`Route`](crate::core::component::Route) en su lugar (su documentación explica el
/// criterio completo para elegir entre ambos).
pub fn route(&self, path: impl Into<RoutePath>) -> RoutePath {
let mut route = path.into();
if !route.is_external() && self.locale.needs_lang_query() {
route.alter_param("lang", self.locale.langid().to_string());
}
route

View file

@ -1,6 +1,8 @@
use crate::html::{Markup, html};
use crate::{AutoDefault, Getters};
use thiserror::Error;
/// Error producido durante el renderizado de un componente.
///
/// Se usa en [`Component::prepare()`](super::Component::prepare) para devolver
@ -22,7 +24,8 @@ use crate::{AutoDefault, Getters};
/// }
/// # }
/// ```
#[derive(AutoDefault, Debug, Getters)]
#[derive(AutoDefault, Debug, Error, Getters)]
#[error("{message}")]
pub struct ComponentError {
/// Mensaje descriptivo del error.
message: String,
@ -57,11 +60,3 @@ impl ComponentError {
self.fallback
}
}
impl std::fmt::Display for ComponentError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}
impl std::error::Error for ComponentError {}

198
src/core/component/route.rs Normal file
View file

@ -0,0 +1,198 @@
use crate::core::component::Context;
use crate::html::RoutePath;
use crate::util;
use std::fmt;
use std::sync::Arc;
/// Encapsula una función que **resuelve una ruta URL** según el contexto de renderizado.
///
/// `Route` envuelve un closure (`Arc<dyn Fn>`) que le permite capturar valores de su entorno,
/// normalmente un identificador u otro dato dinámico, para construir la ruta. También puede
/// clonarse a bajo coste (sólo el `Arc` interno) y compartirse entre hilos.
///
/// Los componentes que acepten una `Route` la invocan con [`resolve()`](Self::resolve) durante el
/// renderizado para obtener la [`RoutePath`] final, que se asigna al atributo HTML correspondiente
/// (normalmente `href` o `action`).
///
/// # ¿Cuál usar, `RoutePath`, `Route` o `Context::route()`?
///
/// [`RoutePath`] es el valor común. Incluye un *path* más una lista de parámetros de consulta, sin
/// ninguna noción de [`Context`] ni de idioma. Es lo que devuelven [`Context::route()`] y
/// [`resolve()`](Self::resolve), y lo que reciben directamente funciones que ya se llaman con `cx`
/// a mano y consumen el resultado de inmediato, como
/// [`Waypoint::append_to()`](crate::response::Waypoint::append_to) /
/// [`Waypoint::or()`](crate::response::Waypoint::or), [`Redirect::*`](crate::response::Redirect) o
/// [`SortLink`](crate::base::component::table::SortLink). Al no depender de `Context`, también
/// sirve fuera del ciclo de renderizado, por ejemplo, para construir la URL de una API externa.
///
/// Entre `Route` y `Context::route()`, cuál usar depende de una sola pregunta: ¿el valor es una
/// propiedad de un componente, que se resolverá más tarde en su propio
/// [`prepare()`](crate::core::component::Component::prepare), o se va a resolver desde una función
/// que ya tiene `cx: &Context`?
///
/// - Los componentes suelen usar `Route` en sus propiedades. Su contenido llega en bruto y se
/// resuelve más tarde, normalmente en la ejecución de su `prepare()`, cuando ya existe un
/// `Context` concreto para esa petición.
///
/// Un literal (`"/path"`) o un `String` se enrutan automáticamente mediante conversión implícita
/// a `Route`, así que no hace falta invocarlo a mano salvo que quieras añadir parámetros
/// adicionales.
///
/// - Si tienes `cx: &Context` (o `&mut Context`) a tu disposición, ya sea en un handler HTTP,
/// dentro de un [`Html::with(|cx| ...)`](crate::base::component::Html::with) o cualquier otra
/// función que reciba `cx`, puedes usar `Context::route()` para obtener el `RoutePath` de
/// inmediato, seguir componiéndolo con `.with_param()`/`.with_flag()`, o insertarlo directamente
/// en el marcado.
///
/// A diferencia de `Route`, un literal pasado directamente a las funciones anteriores **no**
/// reconoce `Context` ni idioma, por lo que habría que llamar a `cx.route(...)` a mano antes de
/// invocarlas (ver el ejemplo con `Waypoint::append_to()` más abajo).
///
/// # Detección automática de URLs externas
///
/// La conversión implícita desde `&str`/`String` reconoce si el texto **parece** una URL externa
/// (ver [`util::url_looks_external()`](crate::util::url_looks_external)) y la trata como tal. En
/// ese caso se comporta como [`Route::external()`], sin pasar por `Context::route()`. Una URL
/// externa nunca debe llevar `lang`, porque no pertenece al espacio de rutas de la aplicación.
///
/// Esta protección no depende sólo de `Route`: [`Context::route()`] también comprueba si el
/// resultado parece externo antes de añadir `lang`, así que llamarlo directamente (por ejemplo, con
/// una URL dinámica que no ha pasado por `Route`) tampoco añade el parámetro.
///
/// Esta detección es una comodidad, no una garantía: se basa en el prefijo del texto. Usa
/// [`Route::external()`] explícitamente cuando quieras dejar clara la intención sin ambigüedad, o
/// para esquemas que la heurística aplicada no reconozca.
///
/// # Ejemplos
///
/// El caso más común es una ruta relativa dependiente del contexto, normalmente usando
/// [`Context::route()`] para que el enlace preserve el parámetro `lang` cuando corresponda:
///
/// ```rust,no_run
/// # use pagetop::prelude::*;
/// let route = Route::with(|cx| cx.route("/path/to/page"));
/// ```
///
/// Un literal o un `String` se convierten directamente, y también pasan por [`Context::route()`]:
///
/// ```rust,no_run
/// # use pagetop::prelude::*;
/// let route: Route = "/path/to/page".into();
/// ```
///
/// Una ruta dinámica puede capturar valores del entorno, algo que un simple puntero a función no
/// permite:
///
/// ```rust,no_run
/// # use pagetop::prelude::*;
/// let user_id = 42;
/// let route = Route::with(move |cx| cx.route(format!("/users/{user_id}")));
/// ```
///
/// Si ya tienes un [`RoutePath`] resuelto -- por ejemplo, combinando
/// [`Context::route()`] con [`Waypoint::append_to()`](crate::response::Waypoint::append_to) --
/// se convierte directamente, sin volver a procesarlo:
///
/// ```rust,no_run
/// # use pagetop::prelude::*;
/// # let cx = Context::new(None);
/// # let waypoint = Waypoint::default();
/// let route: Route = waypoint.append_to(cx.route("/items")).into();
/// ```
///
/// Una URL externa fija, que no debe llevar `lang`, se detecta automáticamente por su prefijo al
/// convertir un literal, y se comporta como [`Route::external()`]:
///
/// ```rust,no_run
/// # use pagetop::prelude::*;
/// let route: Route = "https://www.example.com".into();
/// ```
///
/// [`Route::external()`] sigue siendo útil para dejar la intención explícita, o para esquemas que
/// la heurística no reconozca (por ejemplo una URL construida dinámicamente que no empieza
/// literalmente por uno de los prefijos detectados):
///
/// ```rust,no_run
/// # use pagetop::prelude::*;
/// let route = Route::external("ftp://files.example.com");
/// ```
#[derive(Clone)]
pub struct Route(Arc<dyn Fn(&Context) -> RoutePath + Send + Sync>);
impl Route {
/// Crea una `Route` a partir de un closure que resuelve la ruta según el contexto.
pub fn with<F>(f: F) -> Self
where
F: Fn(&Context) -> RoutePath + Send + Sync + 'static,
{
Route(Arc::new(f))
}
/// Crea una `Route` para una URL externa fija.
///
/// Una URL externa nunca debe llevar el parámetro `lang` ya que no pertenece al espacio de
/// rutas de la aplicación. Un literal o un `String` con pinta de URL externa (ver "Detección
/// automática de URLs externas" más arriba) ya se comportan así al convertirse a `Route`; usa
/// `external()` explícitamente para dejar la intención clara sin ambigüedad, o cuando el
/// heurístico no reconozca el esquema.
pub fn external(url: impl Into<RoutePath>) -> Self {
url.into().into()
}
/// Invoca el closure interno para obtener la [`RoutePath`] final.
pub fn resolve(&self, cx: &Context) -> RoutePath {
(self.0)(cx)
}
}
impl fmt::Debug for Route {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Route")
.field(&"Fn(&Context) -> RoutePath")
.finish()
}
}
impl Default for Route {
fn default() -> Self {
Route::with(|_| RoutePath::default())
}
}
/// Envuelve un [`RoutePath`] que ya tienes construido, sin volver a procesarlo.
///
/// Es el caso de un `RoutePath` obtenido con [`Context::route()`], solo o combinado con
/// [`Waypoint::append_to()`](crate::response::Waypoint::append_to) o
/// [`Waypoint::or()`](crate::response::Waypoint::or): el idioma (`lang`) ya se aplicó ahí, y esta
/// conversión evita volver a aplicarlo.
///
/// Ojo: esta conversión asume que el `RoutePath` ya pasó por `Context::route()`. Si construyes uno
/// a mano (por ejemplo con `RoutePath::new(...)` directamente, sin pasar por `cx.route()`), esta
/// conversión no añadirá `lang` por ti -- simplemente envuelve el valor tal cual, igual que
/// [`Route::external()`].
impl From<RoutePath> for Route {
fn from(path: RoutePath) -> Self {
Route::with(move |_| path.clone())
}
}
impl From<&'static str> for Route {
fn from(path: &'static str) -> Self {
if util::url_looks_external(path) {
Route::external(path)
} else {
Route::with(move |cx| cx.route(path))
}
}
}
impl From<String> for Route {
fn from(path: String) -> Self {
if util::url_looks_external(&path) {
Route::external(path)
} else {
Route::with(move |cx| cx.route(path.clone()))
}
}
}

View file

@ -5,7 +5,7 @@
//! del documento a partir de regiones ([`Region`]). Cada región es un contenedor lógico
//! identificado por un nombre para agrupar y renderizar componentes.
//!
//! Una página ([`Page`](crate::response::page::Page)) es un documento HTML completo. Implementa
//! Una página ([`Page`](crate::response::Page)) es un documento HTML completo. Implementa
//! [`Contextual`](crate::core::component::Contextual) para gestionar su propio [`Context`], donde
//! mantiene el tema activo, la plantilla seleccionada y los componentes asociados a cada región a
//! renderizar.
@ -104,7 +104,7 @@ use crate::{AutoDefault, util};
/// manera distinta.
///
/// El tema decide qué regiones mostrar en el cuerpo del documento, normalmente usando una plantilla
/// ([`Template`]) al renderizar la página ([`Page`](crate::response::page::Page)).
/// ([`Template`]) al renderizar la página ([`Page`](crate::response::Page)).
#[async_trait]
pub trait Region: Send + Sync {
/// Devuelve el nombre de la región.
@ -209,7 +209,7 @@ impl Region for DefaultRegions {
/// Interfaz común para definir plantillas de contenido.
///
/// Una `Template` puede proporcionar una o más variantes para decidir la composición del `<body>`
/// de una página ([`Page`](crate::response::page::Page)). El tema utiliza esta información para
/// de una página ([`Page`](crate::response::Page)). El tema utiliza esta información para
/// determinar qué regiones ([`Region`]) deben renderizarse y en qué orden.
#[async_trait]
pub trait Template: Send + Sync {

View file

@ -6,7 +6,7 @@ use crate::core::theme::{DefaultRegions, DefaultTemplates, TemplateRef};
use crate::global;
use crate::html::{Markup, html};
use crate::locale::L10n;
use crate::response::page::Page;
use crate::response::Page;
use crate::web::http::StatusCode;
/// Interfaz común que debe implementar cualquier tema de PageTop.
@ -63,7 +63,7 @@ pub trait Theme: Extension + Send + Sync {
/// propone como predeterminada.
///
/// Se utiliza al inicializar un [`Context`](crate::core::component::Context) o una página
/// ([`Page`](crate::response::page::Page)) por si no se elige ninguna otra plantilla con
/// ([`Page`](crate::response::Page)) por si no se elige ninguna otra plantilla con
/// [`Contextual::with_template()`](crate::core::component::Contextual::with_template).
///
/// La implementación por defecto devuelve la plantilla [`DefaultTemplates::Standard`] con una
@ -299,12 +299,12 @@ pub trait Theme: Extension + Send + Sync {
/// Permite al tema preparar y componer una página de **error fatal controlado**.
///
/// Esta función decide explícitamente devolver
/// [`ErrorPage::BadRequest`](crate::response::page::ErrorPage::BadRequest),
/// [`ErrorPage::InternalError`](crate::response::page::ErrorPage::InternalError),
/// [`ErrorPage::ServiceUnavailable`](crate::response::page::ErrorPage::ServiceUnavailable) o
/// [`ErrorPage::GatewayTimeout`](crate::response::page::ErrorPage::GatewayTimeout) porque algo
/// ha fallado, pero el servidor sigue activo y el tema, el renderizado y el resto de
/// componentes funcionan con normalidad.
/// [`ErrorPage::BadRequest`](crate::response::ErrorPage::BadRequest),
/// [`ErrorPage::InternalError`](crate::response::ErrorPage::InternalError),
/// [`ErrorPage::ServiceUnavailable`](crate::response::ErrorPage::ServiceUnavailable) o
/// [`ErrorPage::GatewayTimeout`](crate::response::ErrorPage::GatewayTimeout) porque algo ha
/// fallado, pero el servidor sigue activo y el tema, el renderizado y el resto de componentes
/// funcionan con normalidad.
///
/// Por defecto, asigna el título al documento (`title`), se renderiza con la plantilla ya
/// activa en la página (normalmente [`DefaultTemplates::Standard`]) y muestra un componente