Skip to main content

spark/
install.rs

1//! Install hook for Anvil's `ApplicationBuilder`.
2//!
3//! `spark::install(register_fn)` is the canonical one-line opt-in. Use it in
4//! place of your raw `.web(register)` callback:
5//!
6//! ```ignore
7//! Application::builder()
8//!     .web(spark::install(routes::web::register))
9//!     .build();
10//! ```
11//!
12//! What it does:
13//!   1. Wraps the user's routes with the `spark.scope` middleware so any
14//!      `@spark(...)` mounts inside their templates accumulate in a
15//!      per-request task-local for `@sparkScripts` to drain.
16//!   2. Merges Spark's own endpoints — `POST /_spark/update`,
17//!      `GET /_spark/spark.js`, `POST /_spark/auth` — into the web router.
18//!   3. Optionally binds a `BellowsServer` into the container.
19
20use anvil_core::container::Container;
21use anvil_core::route::Router;
22use axum::body::Body;
23use axum::http::Request;
24use axum::middleware::Next;
25use axum::routing::{get, post};
26
27use crate::http;
28
29pub const RUNTIME_PATH: &str = "/_spark/spark.js";
30pub const UPDATE_PATH: &str = "/_spark/update";
31pub const AUTH_PATH: &str = "/_spark/auth";
32
33async fn scope_layer_runner(req: Request<Body>, next: Next) -> axum::response::Response {
34    match crate::middleware::scope_mw(req, next).await {
35        Ok(resp) => resp,
36        Err(err) => {
37            use axum::response::IntoResponse;
38            err.into_response()
39        }
40    }
41}
42
43fn spark_axum_routes() -> axum::Router<Container> {
44    axum::Router::<Container>::new()
45        .route(UPDATE_PATH, post(http::update))
46        .route(RUNTIME_PATH, get(http::runtime_js))
47        .route(AUTH_PATH, post(http::channel_auth))
48        .layer(axum::middleware::from_fn(scope_layer_runner))
49}
50
51/// Merge Spark routes into `router` and wrap every existing route with the
52/// `spark.scope` middleware.
53pub fn install_routes(router: Router) -> Router {
54    router
55        .layer(axum::middleware::from_fn(scope_layer_runner))
56        .adopt(spark_axum_routes())
57}
58
59/// Wrap a user's `.web()` register fn so Spark routes + scope are added in.
60pub fn install<F>(register_fn: F) -> impl FnOnce(Router) -> Router + 'static
61where
62    F: FnOnce(Router) -> Router + 'static,
63{
64    move |r| {
65        let user_router = register_fn(r);
66        install_routes(user_router)
67    }
68}
69
70/// Bind a fresh `BellowsServer` into the container if none is bound yet.
71pub fn ensure_bellows_bound(c: &Container) {
72    if c.resolve::<bellows::BellowsServer>().is_none() {
73        c.bind(bellows::BellowsServer::new());
74    }
75}