diff --git a/README.md b/README.md
index 35023682..674a308c 100644
--- a/README.md
+++ b/README.md
@@ -11,9 +11,8 @@
[](https://crates.io/crates/pagetop)
[](https://git.cillero.es/manuelcillero/pagetop#licencia)
-
-
+
PageTop reivindica la esencia de la web clásica usando [Rust](https://www.rust-lang.org/es) para la
creación de soluciones web SSR (*renderizadas en el servidor*) basadas en HTML, CSS y JavaScript.
@@ -29,8 +28,7 @@ según las necesidades de cada proyecto, incluyendo:
* **Temas** (*themes*): son extensiones que permiten modificar la apariencia de páginas y
componentes.
-
-## ⚡️ Guía rápida
+## Guía rápida
La aplicación más sencilla de PageTop se ve así:
@@ -39,14 +37,14 @@ use pagetop::prelude::*;
#[pagetop::main]
async fn main() -> std::io::Result<()> {
- Application::new().run()?.await
+ Application::new().await.run().await
}
```
Este código arranca el servidor de PageTop. Con la configuración por defecto, muestra una página de
bienvenida accesible desde un navegador local en la dirección `http://localhost:8080`.
-Para personalizar el servicio, se puede crear una extensión de PageTop de la siguiente manera:
+Para personalizar el servicio, puedes crear una extensión de PageTop de la siguiente manera:
```rust,no_run
use pagetop::prelude::*;
@@ -54,28 +52,28 @@ use pagetop::prelude::*;
struct HelloWorld;
impl Extension for HelloWorld {
- fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {
- scfg.route("/", service::web::get().to(hello_world));
+ fn configure_router(&self, router: Router) -> Router {
+ router.route("/", web::get(hello_world))
}
}
async fn hello_world(request: HttpRequest) -> Result {
Page::new(request)
- .add_child(Html::with(|_| html! { h1 { "Hello World!" } }))
+ .with_child(Html::with(|_| html! { h1 { "Hello World!" } }))
.render()
}
#[pagetop::main]
async fn main() -> std::io::Result<()> {
- Application::prepare(&HelloWorld).run()?.await
+ Application::prepare(&HelloWorld).await.run().await
}
```
Este programa implementa una extensión llamada `HelloWorld` que sirve una página web en la ruta raíz
-(`/`) mostrando el texto "Hello world!" dentro de un elemento HTML ``.
+(`/`) mostrando el texto "Hello World!" dentro de un elemento HTML ``.
-## 📂 Proyecto
+## Proyecto
El código se organiza en un *workspace* donde actualmente se incluyen los siguientes subproyectos:
@@ -114,7 +112,7 @@ El código se organiza en un *workspace* donde actualmente se incluyen los sigui
integra [SeaORM](https://www.sea-ql.org/SeaORM) para acceder a bases de datos relacionales.
-## 🧪 Pruebas
+## Pruebas
Para simplificar el flujo de trabajo, el repositorio incluye varios **alias de Cargo** declarados en
`.cargo/config.toml`. Basta con ejecutarlos desde la raíz del proyecto:
@@ -135,14 +133,14 @@ Para simplificar el flujo de trabajo, el repositorio incluye varios **alias de C
> `cargo test`.
-## 🚧 Advertencia
+## Advertencia
**PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su
ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos
hasta que se libere la versión **1.0.0**.
-## 📜 Licencia
+## Licencia
El código está disponible bajo una doble licencia:
@@ -156,7 +154,7 @@ Puedes elegir la licencia que prefieras. Este enfoque de doble licencia es el es
el ecosistema Rust.
-## ✨ Contribuir
+## Contribuir
PageTop mantiene **un único repositorio oficial**:
diff --git a/examples/app-basic.rs b/examples/app-basic.rs
index 53743870..557fdfce 100644
--- a/examples/app-basic.rs
+++ b/examples/app-basic.rs
@@ -2,5 +2,5 @@ use pagetop::prelude::*;
#[pagetop::main]
async fn main() -> std::io::Result<()> {
- Application::new().run()?.await
+ Application::new().await.run().await
}
diff --git a/examples/form-controls.rs b/examples/form-controls.rs
index 4c16b619..4679039a 100644
--- a/examples/form-controls.rs
+++ b/examples/form-controls.rs
@@ -437,5 +437,5 @@ fn form_lists() -> Form {
#[pagetop::main]
async fn main() -> std::io::Result<()> {
- Application::prepare(&FormControls).run()?.await
+ Application::prepare(&FormControls).await.run().await
}
diff --git a/examples/hello-name.rs b/examples/hello-name.rs
index 71439c7d..5ce79561 100644
--- a/examples/hello-name.rs
+++ b/examples/hello-name.rs
@@ -23,5 +23,5 @@ async fn hello_name(
#[pagetop::main]
async fn main() -> std::io::Result<()> {
- Application::prepare(&HelloName).run()?.await
+ Application::prepare(&HelloName).await.run().await
}
diff --git a/examples/hello-world.rs b/examples/hello-world.rs
index f1c40d23..287eb6eb 100644
--- a/examples/hello-world.rs
+++ b/examples/hello-world.rs
@@ -20,5 +20,5 @@ async fn hello_world(request: HttpRequest) -> Result {
#[pagetop::main]
async fn main() -> std::io::Result<()> {
- Application::prepare(&HelloWorld).run()?.await
+ Application::prepare(&HelloWorld).await.run().await
}
diff --git a/examples/intro-colors.rs b/examples/intro-colors.rs
index b219c5be..693a259f 100644
--- a/examples/intro-colors.rs
+++ b/examples/intro-colors.rs
@@ -78,5 +78,5 @@ async fn intro_colors(request: HttpRequest) -> Result {
#[pagetop::main]
async fn main() -> std::io::Result<()> {
- Application::prepare(&IntroColors).run()?.await
+ Application::prepare(&IntroColors).await.run().await
}
diff --git a/examples/navbar-menus.rs b/examples/navbar-menus.rs
index 025a75b3..6c288062 100644
--- a/examples/navbar-menus.rs
+++ b/examples/navbar-menus.rs
@@ -5,6 +5,7 @@ include_locales!(LOC from "examples/locale");
struct SuperMenu;
+#[async_trait]
impl Extension for SuperMenu {
fn dependencies(&self) -> Vec {
vec![
@@ -14,7 +15,7 @@ impl Extension for SuperMenu {
]
}
- fn initialize(&self) {
+ async fn initialize(&self) {
let navbar_menu = bs::Navbar::brand_left(bs::navbar::Brand::new())
.with_expand(token::BreakPoint::LG)
.with_item(bs::navbar::Item::nav(
@@ -114,5 +115,5 @@ impl Extension for SuperMenu {
#[pagetop::main]
async fn main() -> std::io::Result<()> {
- Application::prepare(&SuperMenu).run()?.await
+ Application::prepare(&SuperMenu).await.run().await
}
diff --git a/src/app.rs b/src/app.rs
index 366f900e..6949c50f 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -9,45 +9,40 @@ use crate::response::page::ErrorPage;
use crate::web::{HttpRequest, Router};
use crate::{PAGETOP_VERSION, global, trace};
-use std::future::Future;
use std::io::Error;
use std::sync::LazyLock;
/// Punto de entrada de una aplicación PageTop.
///
-/// No almacena datos, **encapsula** el inicio completo de la configuración y puesta en marcha de la
-/// aplicación. Para instanciarla se puede usar [`new()`](Application::new) o
-/// [`prepare()`](Application::prepare). Después sólo hay que llamar a [`run()`](Application::run)
-/// para ejecutar la aplicación (o a [`test()`](Application::test) si se está preparando un entorno
-/// de pruebas).
+/// Orquesta el arranque de la aplicación. Primero se instancia con [`new()`](Application::new) o
+/// [`prepare()`](Application::prepare), y después se ejecuta usando [`run()`](Application::run) (o
+/// usando [`test()`](Application::test) si se está preparando un entorno de pruebas).
pub struct Application;
-impl Default for Application {
- fn default() -> Self {
- Self::new()
- }
-}
-
impl Application {
/// Crea una instancia mínima de la aplicación, sin extensión raíz.
///
/// Útil para verificar que el servidor arranca correctamente. Para una aplicación real, usa
/// [`prepare()`](Application::prepare) con una extensión raíz.
- pub fn new() -> Self {
- Self::internal_prepare(None)
+ pub async fn new() -> Self {
+ Self::internal_prepare(None).await
}
/// Prepara una instancia de la aplicación a partir de una extensión raíz.
///
- /// Las dependencias se habilitan en orden: primero las que no dependen de ninguna otra, luego
- /// las que dependen de extensiones ya habilitadas, y así sucesivamente hasta dejar habilitada
- /// la extensión raíz.
- pub fn prepare(root_extension: ExtensionRef) -> Self {
- Self::internal_prepare(Some(root_extension))
+ /// Inicializa la aplicación habilitando las extensiones en orden de dependencia: primero las
+ /// que no dependen de ninguna otra, luego las que dependen de extensiones ya habilitadas, y así
+ /// hasta habilitar la extensión raíz.
+ ///
+ /// Es *async* porque cada extensión puede realizar operaciones asíncronas en su
+ /// [`initialize()`](crate::core::extension::Extension::initialize) (conexión a base de datos,
+ /// migraciones, semillas de datos...).
+ pub async fn prepare(root_extension: ExtensionRef) -> Self {
+ Self::internal_prepare(Some(root_extension)).await
}
// Secuencia de arranque común a new() y prepare().
- fn internal_prepare(root_extension: Option) -> Self {
+ async fn internal_prepare(root_extension: Option) -> Self {
// Al arrancar muestra una cabecera para la aplicación.
Self::show_banner();
@@ -64,7 +59,7 @@ impl Application {
extension::all::register_actions();
// Inicializa las extensiones.
- extension::all::initialize_extensions();
+ extension::all::initialize_extensions().await;
Self
}
@@ -119,9 +114,9 @@ impl Application {
/// Arranca el servidor web de la aplicación.
///
- /// Enlaza el puerto del servidor web de forma síncrona (puede fallar con [`std::io::Error`] si
- /// el puerto ya está en uso o el proceso carece de permisos) y devuelve un [`Future`] que
- /// ejecuta el bucle de atención de peticiones. El patrón habitual es:
+ /// Enlaza el puerto del servidor web (puede fallar con [`std::io::Error`] si el puerto ya está
+ /// en uso o el proceso carece de permisos) y ejecuta el bucle de atención de peticiones. El
+ /// patrón habitual es:
///
/// ```rust,no_run
/// use pagetop::prelude::*;
@@ -132,26 +127,24 @@ impl Application {
///
/// #[pagetop::main]
/// async fn main() -> std::io::Result<()> {
- /// Application::prepare(&MyApp).run()?.await
+ /// Application::prepare(&MyApp).await.run().await
/// }
/// ```
- pub fn run(self) -> Result>, Error> {
+ pub async fn run(self) -> Result<(), Error> {
let addr = format!(
"{}:{}",
global::SETTINGS.server.bind_address,
global::SETTINGS.server.bind_port
);
- // Enlaza el puerto de forma síncrona para detectar errores antes del *await*.
+ // Enlaza el puerto de forma síncrona para detectar errores.
let std_listener = std::net::TcpListener::bind(&addr)?;
std_listener.set_nonblocking(true)?;
let router = Self::build_router();
- Ok(async move {
- let listener = tokio::net::TcpListener::from_std(std_listener)?;
- axum::serve(listener, router).await
- })
+ let listener = tokio::net::TcpListener::from_std(std_listener)?;
+ axum::serve(listener, router).await
}
/// Devuelve el servidor web configurado para usarlo en pruebas de integración.
diff --git a/src/core/extension/all.rs b/src/core/extension/all.rs
index bc26cf43..7ceb3fec 100644
--- a/src/core/extension/all.rs
+++ b/src/core/extension/all.rs
@@ -72,10 +72,10 @@ pub fn register_actions() {
// **< INICIALIZA LAS EXTENSIONES >*****************************************************************
-pub fn initialize_extensions() {
+pub async fn initialize_extensions() {
trace::info!("Calling application bootstrap");
for e in EXTENSIONS.get().into_iter().flatten() {
- e.initialize();
+ e.initialize().await;
}
}
diff --git a/src/core/extension/definition.rs b/src/core/extension/definition.rs
index 9187f692..d4025d0a 100644
--- a/src/core/extension/definition.rs
+++ b/src/core/extension/definition.rs
@@ -1,4 +1,5 @@
use crate::actions;
+use crate::async_trait;
use crate::core::AnyInfo;
use crate::core::action::ActionBox;
use crate::core::theme::ThemeRef;
@@ -24,6 +25,7 @@ use crate::web::Router;
/// }
/// }
/// ```
+#[async_trait]
pub trait Extension: AnyInfo + Send + Sync {
/// Nombre de la extensión como *texto localizado* legible para el usuario.
///
@@ -64,10 +66,30 @@ pub trait Extension: AnyInfo + Send + Sync {
None
}
- /// Otras extensiones que deben habilitarse **antes** de esta.
+ /// Extensiones que deben inicializarse **antes** de esta.
///
- /// PageTop resolverá automáticamente estas dependencias respetando el orden durante el arranque
- /// de la aplicación.
+ /// 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;
+ /// # impl Extension for Database {}
+ /// pub struct MyApp;
+ ///
+ /// impl Extension for MyApp {
+ /// fn dependencies(&self) -> Vec {
+ /// vec![&Database]
+ /// }
+ /// }
+ /// ```
fn dependencies(&self) -> Vec {
vec![]
}
@@ -83,9 +105,34 @@ pub trait Extension: AnyInfo + Send + Sync {
/// Inicializa la extensión durante la fase de arranque de la aplicación.
///
- /// Se llama una sola vez, después de que todas las dependencias se han inicializado y antes de
- /// aceptar cualquier petición HTTP.
- fn initialize(&self) {}
+ /// 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.
+ ///
+ /// En ese caso, el bloque `impl Extension` debe llevar `#[async_trait]`:
+ ///
+ /// ```rust,no_run
+ /// use pagetop::prelude::*;
+ ///
+ /// pub struct MyExtension;
+ ///
+ /// #[async_trait]
+ /// impl Extension for MyExtension {
+ /// async fn initialize(&self) {
+ /// // lógica de inicialización...
+ /// }
+ /// }
+ /// ```
+ ///
+ /// Las extensiones que no sobrescriben `initialize()` no necesitan `#[async_trait]`.
+ async fn initialize(&self) {}
/// Registra rutas, servicios y capas de la extensión en el servidor web de la aplicación.
///
@@ -95,12 +142,12 @@ pub trait Extension: AnyInfo + Send + Sync {
///
/// # 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, [...] => "/path")` |
- /// | Estado compartido entre *handlers* | `.with_state(my_state)` |
+ /// | 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
///
diff --git a/src/lib.rs b/src/lib.rs
index 2048c36b..c069150a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -29,7 +29,7 @@ según las necesidades de cada proyecto, incluyendo:
* **Temas** (*themes*): son extensiones que permiten modificar la apariencia de páginas y
componentes.
-# ⚡️ Guía rápida
+## Guía rápida
La aplicación más sencilla de PageTop se ve así:
@@ -38,14 +38,14 @@ use pagetop::prelude::*;
#[pagetop::main]
async fn main() -> std::io::Result<()> {
- Application::new().run()?.await
+ Application::new().await.run().await
}
```
Este código arranca el servidor de PageTop. Con la configuración por defecto, muestra una página de
bienvenida accesible desde un navegador local en la dirección `http://localhost:8080`.
-Para personalizar el servicio, se puede crear una extensión de PageTop de la siguiente manera:
+Para personalizar el servicio, puedes crear una extensión de PageTop de la siguiente manera:
```rust,no_run
use pagetop::prelude::*;
@@ -66,22 +66,12 @@ async fn hello_world(request: HttpRequest) -> Result {
#[pagetop::main]
async fn main() -> std::io::Result<()> {
- Application::prepare(&HelloWorld).run()?.await
+ Application::prepare(&HelloWorld).await.run().await
}
```
Este programa implementa una extensión llamada `HelloWorld` que sirve una página web en la ruta raíz
-(`/`) mostrando el texto "Hello world!" dentro de un elemento HTML ``.
-
-
-# 🧩 Gestión de Dependencias
-
-Los proyectos que utilizan PageTop gestionan las dependencias con `cargo`, como cualquier otro
-proyecto en Rust.
-
-Sin embargo, es fundamental que cada extensión declare explícitamente sus
-[dependencias](core::extension::Extension::dependencies), si las tiene, para que PageTop pueda
-estructurar e inicializar la aplicación de forma modular.
+(`/`) mostrando el texto "Hello World!" dentro de un elemento HTML ``.
*/
#![cfg_attr(docsrs, feature(doc_cfg))]
@@ -132,6 +122,21 @@ use std::ops::Deref;
/// referencia a la versión del *crate* que lo usa.
pub const PAGETOP_VERSION: &str = env!("CARGO_PKG_VERSION");
+/// Re-exporta el atributo [`async_trait`](https://docs.rs/async-trait) para implementar *traits*
+/// con métodos `async`.
+///
+/// Si en el ámbito se declara `use pagetop::prelude::*` o `use pagetop::async_trait;` basta con
+/// usar el nombre corto `#[async_trait]`. Otra opción es usar su forma cualificada
+/// `#[pagetop::async_trait]`.
+///
+/// Al estar incluido en PageTop, no hace falta añadir la dependencia `async-trait` en el
+/// `Cargo.toml` del proyecto.
+///
+/// En proyectos que crean extensiones de PageTop, su uso es necesario cuando se sobrescribe
+/// [`initialize()`](crate::core::extension::Extension::initialize) en un bloque
+/// [`impl Extension`](crate::core::extension::Extension).
+pub use async_trait::async_trait;
+
pub use pagetop_macros::{AutoDefault, builder_fn, html, main, test};
pub use pagetop_statics::{StaticFile, resource};
@@ -174,8 +179,8 @@ pub type UniqueId = std::any::TypeId;
/// Representa el peso lógico de una instancia en una colección ordenada por pesos.
///
-/// Las instancias con pesos **más bajos**, incluyendo valores negativos (`-128..127`), se situarán
-/// antes en la ordenación.
+/// Las instancias con pesos **más bajos**, incluyendo valores negativos, se situarán antes en la
+/// ordenación. Acepta valores `i8` de -128 a 127.
pub type Weight = i8;
// **< API >****************************************************************************************
diff --git a/src/prelude.rs b/src/prelude.rs
index 20d5c800..f87d25b5 100644
--- a/src/prelude.rs
+++ b/src/prelude.rs
@@ -4,7 +4,7 @@
pub use crate::PAGETOP_VERSION;
-pub use crate::{builder_fn, html, main, test};
+pub use crate::{async_trait, builder_fn, html, main, test};
pub use crate::{AutoDefault, CowStr, Getters, StaticResources, UniqueId, Weight};
diff --git a/tests/component_poweredby.rs b/tests/component_poweredby.rs
index 89ff3269..2a82b84f 100644
--- a/tests/component_poweredby.rs
+++ b/tests/component_poweredby.rs
@@ -4,13 +4,13 @@ use pagetop::prelude::*;
///
/// Los tests de este módulo renderizan componentes directamente con `Context::default()`, por lo
/// que sólo necesitan el subsistema de localización y las extensiones registradas, no un router.
-fn setup() {
- let _ = Application::new();
+async fn setup() {
+ Application::new().await;
}
#[pagetop::test]
async fn poweredby_default_shows_only_pagetop_recognition() {
- setup();
+ setup().await;
let mut p = PoweredBy::default();
let html = p.render(&mut Context::default());
@@ -24,7 +24,7 @@ async fn poweredby_default_shows_only_pagetop_recognition() {
#[pagetop::test]
async fn poweredby_new_includes_current_year_and_app_name() {
- setup();
+ setup().await;
let mut p = PoweredBy::new();
let html = p.render(&mut Context::default());
@@ -48,7 +48,7 @@ async fn poweredby_new_includes_current_year_and_app_name() {
#[pagetop::test]
async fn poweredby_with_copyright_overrides_text() {
- setup();
+ setup().await;
let custom = "2001 © FooBar Inc.";
let mut p = PoweredBy::default().with_copyright(Some(custom));
@@ -60,7 +60,7 @@ async fn poweredby_with_copyright_overrides_text() {
#[pagetop::test]
async fn poweredby_with_copyright_none_hides_text() {
- setup();
+ setup().await;
let mut p = PoweredBy::new().with_copyright(None::);
let html = p.render(&mut Context::default());
@@ -72,7 +72,7 @@ async fn poweredby_with_copyright_none_hides_text() {
#[pagetop::test]
async fn poweredby_link_points_to_crates_io() {
- setup();
+ setup().await;
let mut p = PoweredBy::default();
let html = p.render(&mut Context::default());
@@ -85,7 +85,7 @@ async fn poweredby_link_points_to_crates_io() {
#[pagetop::test]
async fn poweredby_getter_reflects_internal_state() {
- setup();
+ setup().await;
// Por defecto no hay copyright.
let p0 = PoweredBy::default();
diff --git a/tests/locale.rs b/tests/locale.rs
index 51ff1863..0bf87868 100644
--- a/tests/locale.rs
+++ b/tests/locale.rs
@@ -1,8 +1,12 @@
use pagetop::prelude::*;
+async fn setup() {
+ Application::new().await;
+}
+
#[pagetop::test]
async fn literal_text() {
- let _app = web::test::init_router(Application::new().test());
+ setup().await;
let l10n = L10n::n("© 2025 PageTop");
assert_eq!(l10n.get(), Some("© 2025 PageTop".to_string()));
@@ -10,7 +14,7 @@ async fn literal_text() {
#[pagetop::test]
async fn translation_without_args() {
- let _app = web::test::init_router(Application::new().test());
+ setup().await;
let l10n = L10n::l("test_hello_world");
let translation = l10n.lookup(&Locale::resolve("es-ES"));
@@ -19,7 +23,7 @@ async fn translation_without_args() {
#[pagetop::test]
async fn translation_with_args() {
- let _app = web::test::init_router(Application::new().test());
+ setup().await;
let l10n = L10n::l("test_hello_user").with_arg("userName", "Manuel");
let translation = l10n.lookup(&Locale::resolve("es-ES"));
@@ -28,7 +32,7 @@ async fn translation_with_args() {
#[pagetop::test]
async fn translation_with_plural_and_select() {
- let _app = web::test::init_router(Application::new().test());
+ setup().await;
let l10n = L10n::l("test_shared_photos").with_args(vec![
("userName", "Roberto"),
@@ -41,7 +45,7 @@ async fn translation_with_plural_and_select() {
#[pagetop::test]
async fn check_fallback_language() {
- let _app = web::test::init_router(Application::new().test());
+ setup().await;
let l10n = L10n::l("test_hello_world");
let translation = l10n.lookup(&Locale::resolve("xx-YY")); // Retrocede a "en-US".
@@ -50,7 +54,7 @@ async fn check_fallback_language() {
#[pagetop::test]
async fn check_unknown_key() {
- let _app = web::test::init_router(Application::new().test());
+ setup().await;
let l10n = L10n::l("non-existent-key");
let translation = l10n.lookup(&Locale::resolve("en-US"));
diff --git a/tests/service.rs b/tests/service.rs
index 5f2ca87b..1c3442f2 100644
--- a/tests/service.rs
+++ b/tests/service.rs
@@ -2,7 +2,7 @@ use pagetop::prelude::*;
#[pagetop::test]
async fn homepage_returns_404() {
- let app = web::test::init_router(Application::new().test());
+ let app = web::test::init_router(Application::new().await.test());
let req = web::test::TestRequest::get().uri("/").to_request();
let resp = web::test::send_request(&app, req).await;
diff --git a/tests/util.rs b/tests/util.rs
index 6dafce8a..a89d6fa0 100644
--- a/tests/util.rs
+++ b/tests/util.rs
@@ -3,6 +3,10 @@ use pagetop::prelude::*;
use std::{borrow::Cow, fs, io};
use tempfile::TempDir;
+async fn setup() {
+ Application::new().await;
+}
+
// **< Testing normalize_ascii() >******************************************************************
fn assert_err(input: &str, expected: util::NormalizeAsciiError) {
@@ -265,7 +269,7 @@ mod unix {
#[pagetop::test]
async fn ok_absolute_dir() -> io::Result<()> {
- let _app = web::test::init_router(Application::new().test());
+ setup().await;
// /tmp//sub
let td = TempDir::new()?;
@@ -279,7 +283,7 @@ mod unix {
#[pagetop::test]
async fn ok_relative_dir_with_manifest() -> io::Result<()> {
- let _app = web::test::init_router(Application::new().test());
+ setup().await;
let td = TempDir::new()?;
let sub = td.path().join("sub");
@@ -293,7 +297,7 @@ mod unix {
#[pagetop::test]
async fn error_not_a_directory() -> io::Result<()> {
- let _app = web::test::init_router(Application::new().test());
+ setup().await;
let td = TempDir::new()?;
let file = td.path().join("foo.txt");
@@ -311,7 +315,7 @@ mod windows {
#[pagetop::test]
async fn ok_absolute_dir() -> io::Result<()> {
- let _app = web::test::init_router(Application::new().test());
+ setup().await;
// C:\Users\...\Temp\...
let td = TempDir::new()?;
@@ -325,7 +329,7 @@ mod windows {
#[pagetop::test]
async fn ok_relative_dir_with_manifest() -> io::Result<()> {
- let _app = web::test::init_router(Application::new().test());
+ setup().await;
let td = TempDir::new()?;
let sub = td.path().join("sub");
@@ -339,7 +343,7 @@ mod windows {
#[pagetop::test]
async fn error_not_a_directory() -> io::Result<()> {
- let _app = web::test::init_router(Application::new().test());
+ setup().await;
let td = TempDir::new()?;
let file = td.path().join("foo.txt");