From 6f82da220bc0c7fe36ba2517bbb72195edf11272 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Thu, 16 Jul 2026 19:54:09 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20(web):=20HttpRequest=20clona=20a?= =?UTF-8?q?hora=20las=20extensiones?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Documenta el nuevo comportamiento y aprovecha para pulir varios comentarios y restringir visibilidad de símbolos internos. --- examples/hello-name.rs | 2 +- src/base/component/html.rs | 2 +- src/core/action/all.rs | 2 +- src/core/component/children.rs | 3 ++- src/core/component/definition.rs | 22 ++++++++++++++----- src/core/component/error.rs | 7 +++--- src/html/assets/javascript.rs | 8 +++---- src/html/assets/preload.rs | 2 +- src/html/assets/stylesheet.rs | 2 +- src/locale/languages.rs | 24 +++++++++++---------- src/web.rs | 37 ++++++++++---------------------- 11 files changed, 55 insertions(+), 56 deletions(-) diff --git a/examples/hello-name.rs b/examples/hello-name.rs index a37051c3..a98cf05f 100644 --- a/examples/hello-name.rs +++ b/examples/hello-name.rs @@ -10,8 +10,8 @@ impl Extension for HelloName { } async fn hello_name( - web::Path(name): web::Path, request: HttpRequest, + web::Path(name): web::Path, ) -> Result { Page::new(request) .with_child(Html::with(move |_| { diff --git a/src/base/component/html.rs b/src/base/component/html.rs index 9cbe8729..9bb74331 100644 --- a/src/base/component/html.rs +++ b/src/base/component/html.rs @@ -21,7 +21,7 @@ use std::sync::Arc; /// }); /// ``` /// -/// Para renderizar contenido que dependa del contexto, se puede acceder a él dentro del *closure*: +/// Para renderizar contenido que dependa del contexto, se puede acceder a él dentro del closure: /// /// ```rust,no_run /// # use pagetop::prelude::*; diff --git a/src/core/action/all.rs b/src/core/action/all.rs index eb531d33..13ad9716 100644 --- a/src/core/action/all.rs +++ b/src/core/action/all.rs @@ -73,7 +73,7 @@ where /// Despacha las funciones asociadas a una [`ActionKey`] con posible salida anticipada. /// -/// Funciona igual que [`dispatch_actions`], pero el *closure* puede devolver +/// 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. /// diff --git a/src/core/component/children.rs b/src/core/component/children.rs index 5bf091a1..cadcb05c 100644 --- a/src/core/component/children.rs +++ b/src/core/component/children.rs @@ -310,7 +310,8 @@ impl Children { } } - // Añade un componente hijo al final de la lista. + // Añade un componente hijo al final de la lista. También lo usa `core::theme::regions` al + // fusionar regiones, fuera de este módulo. #[inline] pub(crate) fn add(&mut self, child: Child) -> &mut Self { self.0.push(child); diff --git a/src/core/component/definition.rs b/src/core/component/definition.rs index ae2340c6..10b0e325 100644 --- a/src/core/component/definition.rs +++ b/src/core/component/definition.rs @@ -110,15 +110,27 @@ pub trait Component: AnyInfo + ComponentClone + ComponentRender + Send + Sync { /// Genera el marcado HTML del componente cuando ningún tema lo sobrescribe. /// - /// Cuarto paso del [ciclo de renderizado](ComponentRender): se invoca tras - /// [`setup()`](Self::setup) y la acción - /// [`BeforeRender`](crate::base::action::component::BeforeRender), pero solamente si ningún - /// tema en la cadena devuelve `Some` en - /// [`Theme::handle_component()`](crate::core::theme::Theme::handle_component). + /// Este es el cuarto paso del [ciclo de renderizado](ComponentRender) tras llamar al + /// [`setup()`](Self::setup) del componente y despachar la acción + /// [`BeforeRender`](crate::base::action::component::BeforeRender) que atiende los cambios de + /// otras extensiones antes de renderizar. Se invoca sólo si ningún tema en la cadena devuelve + /// `Some` en [`Theme::handle_component()`](crate::core::theme::Theme::handle_component) para + /// este componente. + /// + /// Es `async`, a diferencia de [`setup()`](Self::setup), para permitir a los componentes + /// realizar aquí sus llamadas asíncronas, como consultas a base de datos, peticiones a + /// servicios externos, o cualquier operación de E/S, que necesiten para preparar su contenido. /// /// Se recomienda obtener los datos del componente a través de sus propios métodos para que los /// temas puedan implementar `handle_component()` sin depender de los detalles internos. /// + /// Los campos que representen contenido no deben almacenar [`Markup`] ya generado, sino que + /// guardarán el dato en bruto, por ejemplo [`L10n`](crate::locale::L10n) para textos + /// traducibles, o un componente anidado como [`Html`](crate::base::component::Html), o + /// cualquier otro `Component`, normalmente a través de [`Embed`](crate::core::component::Embed) + /// o [`Child`](crate::core::component::Child); y será este método quien lo convierta en HTML en + /// el momento del renderizado, no quien construye la instancia. + /// /// Por defecto, devuelve un [`Markup`] vacío (`Ok(html! {})`). En caso de error, devuelve un /// [`ComponentError`] que puede incluir un marcado alternativo (*fallback*). #[allow(unused_variables)] diff --git a/src/core/component/error.rs b/src/core/component/error.rs index 5b548c62..c631619f 100644 --- a/src/core/component/error.rs +++ b/src/core/component/error.rs @@ -51,10 +51,9 @@ impl ComponentError { // **< ComponentError GETTERS >***************************************************************** - /// Consume el error y devuelve su marcado alternativo. - /// - /// Se invoca internamente en [`ComponentRender`](crate::core::component::ComponentRender). - pub(crate) fn into_fallback(self) -> Markup { + // Consume el error y devuelve su marcado alternativo. Se invoca internamente desde + // `ComponentRender::render()`, en el mismo módulo `core::component`. + pub(super) fn into_fallback(self) -> Markup { self.fallback } } diff --git a/src/html/assets/javascript.rs b/src/html/assets/javascript.rs index 397ee689..c078a532 100644 --- a/src/html/assets/javascript.rs +++ b/src/html/assets/javascript.rs @@ -119,7 +119,7 @@ impl JavaScript { /// Equivale a ``. El parámetro `name` se usa como identificador interno del /// script. /// - /// La función *closure* recibirá el [`Context`] por si se necesita durante el renderizado. + /// La función closure recibirá el [`Context`] por si se necesita durante el renderizado. pub fn inline(name: impl Into, f: F) -> Self where F: Fn(&mut Context) -> String + Send + Sync + 'static, @@ -139,7 +139,7 @@ impl JavaScript { /// /// En condiciones normales, los scripts con `defer` se ejecutan antes de `DOMContentLoaded`. /// - /// La función *closure* recibirá el [`Context`] por si se necesita durante el renderizado. + /// La función closure recibirá el [`Context`] por si se necesita durante el renderizado. pub fn on_load(name: impl Into, f: F) -> Self where F: Fn(&mut Context) -> String + Send + Sync + 'static, @@ -153,11 +153,11 @@ impl JavaScript { /// Crea un **script embebido** con un **handler asíncrono**. /// /// El código se envuelve en un `addEventListener('DOMContentLoaded',async()=>{...})`, que - /// emplea una función `async` para que el cuerpo devuelto por la función *closure* pueda usar + /// emplea una función `async` para que el cuerpo devuelto por la función closure pueda usar /// `await`. Ideal para hidratar la interfaz, cargar módulos dinámicos o realizar lecturas /// iniciales. /// - /// La función *closure* recibirá el [`Context`] por si se necesita durante el renderizado. + /// La función closure recibirá el [`Context`] por si se necesita durante el renderizado. pub fn on_load_async(name: impl Into, f: F) -> Self where F: Fn(&mut Context) -> String + Send + Sync + 'static, diff --git a/src/html/assets/preload.rs b/src/html/assets/preload.rs index 6696c515..d6e68358 100644 --- a/src/html/assets/preload.rs +++ b/src/html/assets/preload.rs @@ -7,7 +7,7 @@ use crate::{AutoDefault, CowStr, Weight, util}; // Informa al navegador de la naturaleza del recurso para que pueda asignarle la prioridad correcta // y aplicar la política de caché adecuada. #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] -pub(crate) enum AsType { +enum AsType { // Fuente web (`@font-face`). Implica `crossorigin` automático. #[default] Font, diff --git a/src/html/assets/stylesheet.rs b/src/html/assets/stylesheet.rs index d106ae8e..ff3aaf53 100644 --- a/src/html/assets/stylesheet.rs +++ b/src/html/assets/stylesheet.rs @@ -100,7 +100,7 @@ impl StyleSheet { /// Equivale a ``. El parámetro `name` se usa como identificador interno del /// recurso. /// - /// La función *closure* recibirá el [`Context`] por si se necesita durante el renderizado. + /// La función closure recibirá el [`Context`] por si se necesita durante el renderizado. pub fn inline(name: impl Into, f: F) -> Self where F: Fn(&mut Context) -> String + Send + Sync + 'static, diff --git a/src/locale/languages.rs b/src/locale/languages.rs index cda4483d..13b5e913 100644 --- a/src/locale/languages.rs +++ b/src/locale/languages.rs @@ -5,17 +5,19 @@ use super::{LanguageIdentifier, langid}; use std::collections::HashMap; use std::sync::LazyLock; -/// Tabla de idiomas soportados por PageTop. -/// -/// Cada entrada asocia un código de idioma en minúsculas (por ejemplo, `"en"` o `"es-es"`) con: -/// -/// - Su [`LanguageIdentifier`] canónico. -/// - La clave de traducción definida en `src/locale/{lang}/languages.ftl` para mostrar su nombre en -/// el idioma activo. -/// -/// Esto permite admitir alias de idioma como `"en"` o `"es"` y, al mismo tiempo, mantener un -/// identificador de idioma canónico (por ejemplo, `langid!("en-US")` o `langid!("es-ES")`). -pub(crate) static LANGUAGES: LazyLock> = +// Tabla de idiomas soportados por PageTop. +// +// Cada entrada asocia un código de idioma en minúsculas (por ejemplo, "en" o "es-es") con: +// +// - Su `LanguageIdentifier` canónico. +// - La clave de traducción definida en `src/locale/{lang}/languages.ftl` para mostrar su nombre en +// el idioma activo. +// +// Esto permite admitir alias de idioma como "en" o "es" y, al mismo tiempo, mantener un +// identificador de idioma canónico (por ejemplo, `langid!("en-US")` o `langid!("es-ES")`). +// +// Sólo lo usa `locale::definition`, en el mismo módulo `locale`. +pub(super) static LANGUAGES: LazyLock> = LazyLock::new(|| { util::kv![ "en" => ( langid!("en-US"), "english" ), diff --git a/src/web.rs b/src/web.rs index 9424a0e3..a0156e3f 100644 --- a/src/web.rs +++ b/src/web.rs @@ -66,25 +66,12 @@ use std::task::{Context, Poll}; /// } /// ``` /// -/// # Orden de parámetros en el handler -/// -/// `HttpRequest` toma las extensiones de la petición al extraerse. Los extractores que leen de ahí -/// (como `Path` o `Extension`) deben aparecer **antes** en la lista de parámetros del -/// handler: -/// -/// ```rust,no_run -/// # use pagetop::prelude::*; -/// // Correcto: Path se declara antes que HttpRequest. -/// async fn view_post( -/// web::Path(id): web::Path, -/// request: HttpRequest, -/// ) -> Result { -/// # todo!() -/// } -/// ``` -/// -/// Los extractores que no dependen de las extensiones, como `Query` o `Method`, no están sujetos -/// a esta restricción. +/// `HttpRequest` no consume las extensiones de la petición, por lo que el resto de extractores del +/// handler que también las necesiten (como `Path`, `Extension`...) las siguen viendo +/// intactas, sea cual sea la posición en la que se declare `HttpRequest`. Por convención, se +/// declara como primer parámetro del handler. El único orden que sigue siendo obligatorio es el que +/// impone Axum: un extractor que consuma el cuerpo de la petición (`Form`, `RawForm`...) debe ir +/// siempre el último. #[derive(Clone, Debug)] pub struct HttpRequest { uri: http::Uri, @@ -132,20 +119,18 @@ impl HttpRequest { impl FromRequestParts for HttpRequest { type Rejection = Infallible; - // Extrae la petición y toma las extensiones que han sido inyectadas por middleware. Las - // extensiones se mueven a un `Arc` compartido para que `HttpRequest` sea `Clone`. - // - // Nota: tras este extractor `parts.extensions` queda vacío; otros extractores que dependan de - // `Extension` deben registrarse antes en la cadena del handler. + // Clona (no toma) las extensiones inyectadas por middleware, para que otros extractores del + // handler (`Path`, `Extension`...) las sigan viendo intactas sin importar en qué posición + // se declare `HttpRequest`. El clon se envuelve en un `Arc` compartido para que `HttpRequest` + // sea `Clone` a coste mínimo en el resto de su ciclo de vida. async fn from_request_parts( parts: &mut http::request::Parts, _state: &S, ) -> Result { - let extensions = std::mem::take(&mut parts.extensions); Ok(HttpRequest { uri: parts.uri.clone(), headers: parts.headers.clone(), - extensions: Arc::new(extensions), + extensions: Arc::new(parts.extensions.clone()), }) } }