crates.io ↗

iet-http-htmx-axum

feature adapter official

Renders server-side HTML, validates forms, and supports HTMX without requiring JavaScript.

CoreFeaturesIntegrationsStacksTooling
Used byiet-auth-login-htmx-axum
iet-http-htmx-axumadapterHTML/HTMX transport adapter
Depends oniet-corecontractiet-uicontract

How to use it

iet-http-htmx-axum provides HTML/HTMX transport helpers on top of Axum: HtmlPage/HtmlFragment responses, HtmxRequest for reading presentation hints, safe redirect/htmx_redirect, HtmlError for private error pages, and a bounded ValidatedForm extractor. These are additive pieces an app merges with its own Axum routes.

rust
// crates/features/iet-http-htmx-axum/tests/forms.rs
use iet_core::{AppResult, Validate};
use iet_http_htmx_axum::ValidatedForm;

#[derive(serde::Deserialize)]
struct Search { query: String }

impl Validate for Search {
    fn validate(&self) -> AppResult<()> {
        if self.query.trim().is_empty() || self.query.len() > 100 {
            return Err(iet_core::AppError::new(
                iet_core::ErrorKind::Validation, "search.input", "Invalid search input"));
        }
        Ok(())
    }
}

async fn submit(ValidatedForm(form): ValidatedForm<Search>) -> String {
    form.query
}
  • ValidatedForm enforces the form content type, a 16 KiB body limit, and Validate; rejections are 415, 400, 413, or 422 and never echo the body.
  • Read hints with HtmxRequest::new(&headers).is_htmx() / .target(); turn an AppError into a page with HtmlError(error) (adds no-store).
  • redirect(&headers, "/path") selects HX-Redirect for HTMX requests and 303 Location otherwise, and rejects non-local paths.

How it works

ValidatedForm is an Axum FromRequest extractor that rejects untrusted input before the handler. decode_form checks the URL-encoded content type, reads the body under a byte limit, deserializes with serde_urlencoded, and then Validate::validate runs, mapping each failure to a plain, non-reflective status.

flowchart LR
  A["ValidatedForm::from_request"] --> B["decode_form"]
  B --> C{"form content type"}
  C -->|no| D["415 Unsupported Media Type"]
  C -->|yes| E["to_bytes with 16 KiB limit"]
  E -->|over| F["413 Payload Too Large"]
  E --> G["serde_urlencoded::from_bytes"]
  G --> H["Validate::validate"]
  H -->|invalid| I["422 Unprocessable Entity"]
  H -->|valid| J["handler receives T"]

What it implements

  • Concrete mechanism; see the public API below.

What's inside

Modules

  • src/askama.rs
  • src/assets.rs
  • src/cache.rs
  • src/compiled_ui.rs
  • src/deferred.rs
  • src/form.rs
  • src/lib.rs
  • src/request.rs
  • src/response.rs

Public API

askama

  • struct AskamaPage — Render an Askama template as a complete HTML document. Askama's HTML autoescaping is retained; template rendering failures map to 500.
  • struct AskamaFragment — Render an Askama template as an HTML fragment. Askama's HTML autoescaping is retained; template rendering failures map to 500.

assets

  • const HTMX_JS
  • const ALPINE_JS
  • struct BundledUiAssets
  • pub fn with_htmx(htmx: &'static [u8], content_hash: &'static str) -> UiAssets — Mount bundled Alpine and Tailwind files at versioned routes alongside pinned HTMX bytes.
  • pub fn router(content_hash: &'static str) -> axum::Router — Mount all bundled assets at versioned routes.
  • pub fn bundled_router() -> axum::Router — Mount the bundled releases at their release-derived immutable version path.
  • pub fn bundled_tags() -> String — Return script tags for [Self::bundled_router].
  • struct UiAssets — Build routes that serve local HTMX, Alpine.js, and Tailwind browser-build assets. The provided bytes must be locally built or vendor-pinned files. The framework intentionally does not fetch CDN content or impose a Node build step. Supply a version token that changes whenever any asset changes before enabling immutable caching.
  • const fn — Construct the asset bundle. content_hash should identify these exact asset bytes.
  • pub fn router(self) -> axum::Router — Return a router serving the asset paths referenced by [Self::tags].
  • pub fn tags(&self) -> String — Return asset tags that match the mounted versioned routes.
  • pub fn tags(content_hash: &str) -> String — Return matching deferred script tags for all bundled UI assets.

cache

  • pub fn no_store(mut response: Response) -> Response — Mark a response private, including browser and intermediary caches.
  • pub fn public(mut response: Response, request: &HeaderMap, etag: &str, seconds: u32) -> Response — Cache a successful public representation with a caller-supplied quoted ETag. The validator must identify the entire representation, not just its route.

compiled_ui

  • pub fn script_router(scripts: &[Script]) -> Router — Register content-addressed JavaScript modules once at the composition root.
  • const COMPONENT_RUNTIME_SOURCE
  • pub fn component_router(scripts: &[Script]) -> Router

deferred

  • struct DeferredResponse — Versioned NDJSON envelopes containing HTML, never client-side domain state. Up to 16 concurrent jobs, each with a timeout and a 1 MiB HTML limit.
  • pub fn new() -> Self
  • pub fn timeout(mut self, timeout: Duration) -> Self
  • pub fn boundary<F>(mut self, id: &str, render: F) -> AppResult<Self> where F: Future<Output = AppResult<String>> + Send + 'static,
  • pub fn runtime_router() -> axum::Router — Small optional runtime; mount once beside the existing HTMX asset router.

form

  • struct ValidatedForm — Bounded URL-encoded form data validated before reaching a handler.

request

  • struct HtmxRequest
  • pub fn new(headers: &'a HeaderMap) -> Self
  • pub fn is_htmx(self) -> bool
  • pub fn is_boosted(self) -> bool
  • pub fn target(self) -> Option<&'a str>
  • pub fn current_url(self) -> Option<&'a str>
  • pub fn vary(self, response: &mut axum::http::Response<axum::body::Body>)

response

  • struct HtmlError — Safe browser error presentation. Never reflects application messages or fields.
  • pub fn redirect(headers: &axum::http::HeaderMap, path: &str) -> Result<Response, AppError> — Select redirect semantics from presentation hints, never authorization hints.
  • struct HtmlPage
  • struct HtmlFragment
  • struct HtmlRedirect
  • struct HtmlResponse
  • struct CompiledHtml
  • pub fn page(self) -> HtmlPage<T>
  • pub fn fragment(self) -> HtmlFragment<T>
  • pub fn htmx_redirect(path: &str) -> Result<Response, AppError> — Create an HTMX redirect response after validating a same-origin path.
  • pub fn safe_local_path(path: &str) -> bool

Impl map

  • IntoResponse for AskamaPage<T>
  • IntoResponse for AskamaFragment<T>
  • Default for DeferredResponse
  • IntoResponse for DeferredResponse
  • FromRequest<S> for ValidatedForm<T>
  • From<AppError> for HtmlError
  • IntoResponse for HtmlError
  • From<iet_ui::Markup> for HtmlPage<CompiledHtml>
  • From<iet_ui::Markup> for HtmlFragment<CompiledHtml>
  • IntoResponse for HtmlPage<CompiledHtml>
  • IntoResponse for HtmlFragment<CompiledHtml>
  • IntoResponse for HtmlPage<T>
  • IntoResponse for HtmlFragment<T>
  • IntoResponse for HtmlRedirect<'_>
  • Validate for Search

Depends on

External crates: askama, axum, futures-util, http-body-util, include_dir, serde, serde_json, serde_urlencoded, tokio, tracing

Cargo features

  • default — (no dependencies)
  • askama — dep:askama
  • ui-assets — dep:include_dir
  • progressive — dep:futures-util, dep:serde_json, dep:tokio
  • compiled-ui — dep:iet-ui
  • components — compiled-ui, iet-ui/scripts

Used by

In this repository

  • examples/htmx-starter
  • examples/skills-only-app
  • examples/skills-only-app-task-tracker

Source: crates/features/iet-http-htmx-axum