Skip to main content

autumn_web/
lib.rs

1//! # Autumn
2//!
3//! An opinionated, convention-over-configuration web framework for Rust.
4//!
5//! Autumn assembles proven Rust crates ([Axum], [Maud], [Diesel], htmx, Tailwind)
6//! into a Spring Boot-style developer experience with proc-macro-driven
7//! conventions and customization options at every level.
8//!
9//! ## Quick start
10//!
11//! ```rust,no_run
12//! use autumn_web::prelude::*;
13//!
14//! #[get("/")]
15//! async fn index() -> Markup {
16//!     html! { h1 { "Hello, Autumn!" } }
17//! }
18//!
19//! #[autumn_web::main]
20//! async fn main() {
21//!     autumn_web::app()
22//!         .routes(routes![index])
23//!         .run()
24//!         .await;
25//! }
26//! ```
27//!
28//! ## Architecture overview
29//!
30//! | Layer | Crate | Purpose |
31//! |-------|-------|---------|
32//! | HTTP server | [Axum] | Routing, extractors, middleware |
33//! | HTML templates | [Maud] | Type-safe, compiled HTML via `html!` macro |
34//! | Database | [Diesel] | Async Postgres via `diesel-async` + deadpool |
35//! | Client interactivity | htmx | Embedded JS served from same-origin `/static/js/` routes |
36//! | Styling | Tailwind CSS | Downloaded + managed by `autumn-cli` |
37//!
38//! ## Modules
39//!
40//! - [`mod@app`] -- Application builder for configuring and launching the server.
41//! - [`config`] -- Layered configuration: defaults, `autumn.toml`, env overrides.
42//! - [`db`] -- Database connection pool and the [`Db`] request extractor.
43//! - [`error`] -- Framework error type ([`AutumnError`]) and result alias.
44//! - [`extract`] -- Re-exported Axum extractors ([`Form`],
45//!   [`Json`], [`Path`], [`Query`], and optional multipart support).
46//! - [`health`] -- Compatibility alias for readiness plus legacy health helpers.
47
48//! - [`middleware`] -- Built-in middleware (request IDs).
49//! - [`pagination`] -- Standardized `page`/`size` extractor and response wrapper.
50//! - [`prelude`] -- Glob import for the most common types.
51
52//!
53//! ## Zero-config defaults
54//!
55//! An Autumn app runs out of the box with no configuration file. Every
56//! setting has a sensible default (port 3000, `info` log level, etc.).
57//! Override via `autumn.toml` or `AUTUMN_*` environment variables.
58//! See [`config::AutumnConfig`] for the full list.
59//!
60//! [Axum]: https://docs.rs/axum
61//! [Maud]: https://maud.lambda.xyz
62//! [Diesel]: https://diesel.rs
63
64// Allow `::autumn_web::` paths generated by proc macros to resolve
65// within this crate itself (needed for tests and doctests).
66#[allow(unused_extern_crates)]
67extern crate self as autumn_web;
68
69/// Typed accessible UI primitives (issue #1706).
70///
71/// These make the accessible name a compile-time obligation. See [`mod@a11y`].
72#[cfg(feature = "maud")]
73pub mod a11y;
74pub mod actuator;
75pub mod aggregate;
76/// Operator alerts for built-in failure conditions.
77///
78/// Connects built-in failure signals (dead-lettered jobs, Down health
79/// indicators, 5xx-rate spikes, scheduled-task failures) to the app's
80/// configured mailer and signed outbound webhook behind `[alerts]` config —
81/// with zero application code. See [`mod@alerts`].
82pub mod alerts;
83pub mod app;
84pub mod assets;
85pub mod audit;
86pub mod auth;
87pub mod authorization;
88pub mod batches;
89pub mod build_info;
90pub mod cache;
91#[cfg(feature = "ws")]
92pub mod channels;
93#[cfg(feature = "ws")]
94pub use channels::{
95    Broadcast, BroadcastError, BroadcastPayload, ChannelBackendConfigError, ChannelMessage,
96    ChannelPublishError, ChannelStats, Channels, ChannelsBackend, LocalChannelsBackend,
97};
98pub mod canary;
99pub mod circuit_breaker;
100pub mod config;
101pub mod credentials;
102pub mod current;
103#[cfg(feature = "db")]
104pub mod db;
105pub mod dotenv;
106pub mod download;
107pub mod encryption;
108pub mod error;
109#[cfg(feature = "maud")]
110pub mod error_pages;
111pub mod extract;
112/// Deterministic fake-data generation backing factory `.fake()` support.
113pub mod fake;
114pub mod feed;
115/// View-layer value formatting helpers (currency, delimited numbers,
116/// pluralize, truncate, relative/absolute dates) for Maud templates.
117///
118/// See [`mod@format`] for the full API.
119pub mod format;
120pub mod health;
121#[cfg(feature = "db")]
122pub mod hooks;
123#[cfg(feature = "i18n")]
124pub mod i18n;
125pub mod idempotency;
126pub mod range;
127pub mod seo;
128/// Translation lookup macro with compile-time key validation.
129///
130/// Re-exported from [`crate::i18n::t`] for ergonomic
131/// `autumn_web::t!(locale, "key")` usage.
132#[cfg(feature = "i18n")]
133pub use crate::i18n::t;
134#[cfg(feature = "inbound-mail")]
135pub mod inbound_mail;
136pub mod inspector;
137pub mod interceptor;
138#[cfg(feature = "mail")]
139pub mod mail;
140pub mod maintenance;
141#[cfg(feature = "managed-pg")]
142pub mod managed_pg;
143#[cfg(feature = "db")]
144pub mod migrate;
145pub(crate) mod pg_conn_str;
146pub mod plugin;
147pub mod plugin_conformance;
148pub mod probe;
149
150/// Re-export of the [`include_dir`](https://docs.rs/include_dir) crate.
151///
152/// Lets apps embed their `static/` and `i18n/` trees without adding `include_dir`
153/// as a direct dependency. Used by the [`embed_static!`] and [`embed_locales!`]
154/// macros. Nested in a module (rather than a crate-root `pub use`) so the
155/// re-export resolves for external consumers, mirroring [`reexports`].
156#[cfg(feature = "embed-assets")]
157pub mod include_dir {
158    pub use ::include_dir::*;
159}
160
161/// Embed the app's `static/` directory (including the `.autumn-manifest.json`
162/// written by `autumn build --embed`) into the binary at compile time.
163///
164/// Expands to an [`include_dir::Dir`] rooted at the **calling crate's**
165/// `static/` directory (resolved via `$CARGO_MANIFEST_DIR`, exactly like
166/// `embed_migrations!`). Pass the result to
167/// [`AppBuilder::embedded_static`](crate::app::AppBuilder::embedded_static):
168///
169/// ```rust,ignore
170/// static STATIC: autumn_web::include_dir::Dir = autumn_web::embed_static!();
171///
172/// #[autumn_web::main]
173/// async fn main() {
174///     autumn_web::app().embedded_static(&STATIC).run().await;
175/// }
176/// ```
177#[cfg(feature = "embed-assets")]
178#[macro_export]
179macro_rules! embed_static {
180    () => {{
181        // `include_dir!` emits `include_dir::{Dir, File, ...}` paths resolved at
182        // the call site, so bring our re-export into scope under that name. This
183        // lets apps embed without depending on the `include_dir` crate directly.
184        #[allow(unused_imports)]
185        use $crate::include_dir;
186        $crate::include_dir::include_dir!("$CARGO_MANIFEST_DIR/static")
187    }};
188}
189
190/// Embed the app's i18n locale bundles (the `i18n/` directory, or a custom
191/// directory) into the binary at compile time.
192///
193/// Pass the result to
194/// [`AppBuilder::embedded_locales`](crate::app::AppBuilder::embedded_locales).
195///
196/// ```rust,ignore
197/// static LOCALES: autumn_web::include_dir::Dir = autumn_web::embed_locales!();
198/// // or a custom directory:
199/// static LOCALES: autumn_web::include_dir::Dir = autumn_web::embed_locales!("translations");
200/// ```
201#[cfg(all(feature = "embed-assets", feature = "i18n"))]
202#[macro_export]
203macro_rules! embed_locales {
204    () => {{
205        #[allow(unused_imports)]
206        use $crate::include_dir;
207        $crate::include_dir::include_dir!("$CARGO_MANIFEST_DIR/i18n")
208    }};
209    ($dir:literal) => {{
210        #[allow(unused_imports)]
211        use $crate::include_dir;
212        $crate::include_dir::include_dir!(concat!("$CARGO_MANIFEST_DIR/", $dir))
213    }};
214}
215#[cfg(feature = "system-info")]
216pub mod system_info;
217pub use plugin::{Plugin, Plugins};
218
219pub mod route_listing;
220
221/// Inbound (server-side) TLS support (issue #1603).
222///
223/// Load and validate a certificate + key, build a reloadable rustls
224/// `ServerConfig`, and inspect leaf-certificate expiry. Gated behind the
225/// off-by-default `tls` feature.
226#[cfg(feature = "tls")]
227pub mod tls;
228
229/// Automatic ACME (Let's Encrypt) certificate provisioning + renewal (#1608).
230///
231/// Builds on the [`tls`] listener: the certificate obtained over the ACME
232/// HTTP-01 challenge hot-swaps into the same `ReloadableCertResolver` the TLS
233/// listener serves. Gated behind the off-by-default `acme` feature.
234#[cfg(feature = "acme")]
235pub mod acme;
236
237#[cfg(feature = "db")]
238pub mod sharding;
239
240#[cfg(feature = "db")]
241pub mod repository;
242#[cfg(feature = "db")]
243pub(crate) mod repository_commit_hooks;
244#[cfg(feature = "db")]
245pub use repository::RepositoryError;
246
247/// Read-your-own-writes routing support.
248///
249/// When `database.read_your_writes` is `request` or `session`, generated
250/// repository read methods consult the per-request task-local at acquire time
251/// and redirect replica-eligible reads to the primary when a write has
252/// occurred in the same request (or within the session cookie window).
253#[cfg(feature = "db")]
254pub mod read_your_writes;
255
256/// Offline-first local SQLite store and background sync engine for
257/// occasionally-connected apps (e.g. Tauri mobile).
258///
259/// See the [`sync`] module documentation for the architecture (change
260/// tracking, tombstoning, conflict resolution) and wiring examples.
261#[cfg(feature = "offline-sync")]
262pub mod sync;
263
264/// Automatic record version history for `#[repository]` writes.
265///
266/// See [`version_history`] module documentation for the full API.
267pub mod version_history;
268pub use version_history::{
269    ColumnChange, VersionEntry, VersionFilter, VersionOp, VersionPage, VersionedRecord,
270    compute_delete_changes, compute_diff, compute_insert_changes,
271};
272
273/// Router construction and integration with Axum.
274///
275/// This module is responsible for taking the application's configuration,
276/// defined routes, middleware, and state, and building the final `axum::Router`
277/// that will handle incoming HTTP requests.
278pub(crate) mod router;
279
280/// Fuzzing-only re-export surface.
281///
282/// Compiled only when the crate is built with `--cfg fuzzing` (as cargo-fuzz
283/// does for the workspace-root `fuzz/` crate). It re-exports otherwise-private
284/// or `pub(crate)` request-path parsing seams so the fuzz targets can drive
285/// them over raw untrusted bytes. Everything here is guarded by `#[cfg(fuzzing)]`
286/// so normal builds — and `cargo package` output — are byte-identical.
287///
288/// Not part of the public API; no stability guarantees.
289#[cfg(fuzzing)]
290#[doc(hidden)]
291pub mod __fuzz {
292    // Routing / path-parameter extraction (functions are `pub` inside the
293    // `pub(crate)` `router` module).
294    pub use crate::router::{
295        extract_host_without_port, join_nested_path, path_matches_route_prefix,
296    };
297    // `extract_path_params` only exists under the `openapi` feature (it backs
298    // spec-URL generation); the `fuzz/` crate enables `openapi` so this routing
299    // seam is present when fuzzing.
300    #[cfg(feature = "openapi")]
301    pub use crate::router::extract_path_params;
302
303    // Trusted-proxy / `X-Forwarded-*` header parsing.
304    pub use crate::security::trusted_proxies::{
305        __fuzz_parse_forwarded_ip as parse_forwarded_ip,
306        __fuzz_parse_trusted_proxy as parse_trusted_proxy,
307        __fuzz_resolve_forwarded as resolve_forwarded,
308    };
309
310    // Cookie / signed-session decode.
311    pub use crate::session::__fuzz_decode_cookie as decode_cookie;
312
313    // Body handling: form-urlencoded (always) + inbound-mail MIME (feature-gated).
314    pub use crate::form::__fuzz_decode_urlencoded as decode_urlencoded_form;
315    #[cfg(feature = "inbound-mail")]
316    pub use crate::inbound_mail::{
317        __fuzz_parse_address_list as parse_address_list, __fuzz_parse_generic as parse_generic,
318        __fuzz_parse_ses as parse_ses,
319    };
320}
321
322#[cfg(feature = "db")]
323pub use hooks::{
324    DraftField, FieldDiff, MutationContext, MutationHooks, MutationOp, NoHooks, Patch, UpdateDraft,
325};
326pub mod etag;
327/// Traced outbound HTTP client with retries and test mocks.
328///
329/// See [`http_client::Client`] for the full API. The module is exposed as
330/// `autumn_web::http` so handler code can write `use autumn_web::http::Client`.
331#[cfg(feature = "http-client")]
332pub mod http_client;
333#[cfg(feature = "http-client")]
334pub use http_client as http;
335#[cfg(feature = "flash")]
336pub mod flash;
337#[cfg(feature = "htmx")]
338pub mod htmx;
339/// Declarative live-broadcast trait for `#[repository(Model, broadcasts = "topic")]`.
340///
341/// Implement [`live::LiveFragment`] on a model to enable automatic `hx-swap-oob`
342/// broadcasts after each `save`/`update`/`delete_by_id` call. Requires the
343/// `ws`, `maud`, and `htmx` features.
344#[cfg(all(feature = "htmx", feature = "maud"))]
345pub mod live;
346pub mod lock;
347pub mod log;
348pub(crate) mod logging;
349/// Project typed JSON endpoints as Model Context Protocol (MCP) tools so AI
350/// agents can call the real, authenticated handler pipeline.
351///
352/// Enable with the Cargo feature `mcp` (which implies `openapi`).
353#[cfg(feature = "mcp")]
354pub mod mcp;
355pub mod middleware;
356/// Content-negotiated success responder (`Negotiate` / `Negotiated` / `Format`).
357#[cfg(feature = "maud")]
358pub mod negotiate;
359pub mod openapi;
360pub mod pagination;
361pub mod paths;
362/// Eager-loading (preload) runtime for `#[model]` associations.
363///
364/// See [`preload`] for [`preload::Preloaded`], [`preload::NotLoaded`], and the
365/// [`preload::Preloadable`] trait that generated code implements.
366pub mod preload;
367pub mod prelude;
368pub use paths::PathExt;
369#[cfg(feature = "presence")]
370pub mod presence;
371#[cfg(all(feature = "presence", feature = "maud"))]
372pub use presence::presence_badge;
373#[cfg(all(
374    feature = "presence",
375    feature = "ws",
376    feature = "maud",
377    feature = "htmx"
378))]
379pub use presence::presence_stream;
380#[cfg(feature = "presence")]
381pub use presence::{Presence, PresenceEntry, PresenceEvent, PresenceHandle};
382pub(crate) mod route;
383pub use route::{RepositoryApiMeta, Route, RouteIdempotency, RouteTimeout};
384/// First-class Markdown rendering with frontmatter parsing and SSG integration.
385///
386/// Enable with the Cargo feature `markdown`.
387#[cfg(feature = "markdown")]
388pub mod markdown;
389/// Pluggable error reporting: catch handler panics and route panics + 5xx
390/// responses to configured [`ErrorReporter`](reporting::ErrorReporter)s.
391///
392/// Enabled by the `reporting` Cargo feature (on by default).
393#[cfg(feature = "reporting")]
394pub mod reporting;
395pub mod scheduler;
396pub mod security;
397pub mod session;
398#[cfg(feature = "redis")]
399pub(crate) mod session_redis;
400pub mod sse;
401/// Static site generation support.
402pub mod static_gen;
403pub mod step_up;
404#[cfg(feature = "storage")]
405pub mod storage;
406pub mod tenancy;
407pub mod tenant_cell;
408pub mod time;
409pub mod time_zone;
410pub mod user_agent;
411
412pub mod events;
413pub mod experiments;
414pub mod feature_flags;
415pub mod form;
416pub mod gdpr;
417pub mod job;
418pub mod job_tracking;
419/// Safe, method-aware link helpers: [`links::link_to`] anchors and
420/// [`links::button_to`] CSRF-protected action buttons.
421pub mod links;
422pub mod nested_form;
423pub mod payload_version;
424pub mod runtime_config;
425#[cfg(feature = "seed")]
426pub mod seed;
427
428// ── #1343 AC4: fake-seeder registration forwarding ──────────────────────────
429//
430// `#[model]` emits a call to `autumn_web::__autumn_register_fake_seeder!` for
431// every model so `autumn seed --count N --model M` can find and run the model's
432// factory without the user editing `src/bin/seed.rs`. The registration must
433// exist *exactly* when `autumn_web::seed::FakeSeeder` and the db-backed
434// `create_many` do — i.e. when this crate is built with the `seed` feature.
435//
436// A downstream `#[cfg(feature = "seed")]` in the emitted code would test the
437// *application* crate's features, not autumn-web's, so it can't be used
438// directly. Instead we forward through a `#[macro_export]` macro whose two
439// cfg-gated definitions are resolved against *autumn-web's* features at the
440// point autumn-web is compiled: the real `inventory::submit!` when `seed` is on,
441// and a no-op otherwise. This keeps models compiling unchanged when seeding is
442// disabled (e.g. autumn-web's own default-feature test build).
443
444/// Register a model's factory as a CLI-callable fake seeder (internal; invoked
445/// by `#[model]`). Expands to an `inventory::submit!` of a
446/// [`seed::FakeSeeder`](crate::seed::FakeSeeder).
447#[cfg(feature = "seed")]
448#[macro_export]
449#[doc(hidden)]
450macro_rules! __autumn_register_fake_seeder {
451    ($model:ty, $name:expr) => {
452        $crate::reexports::inventory::submit! {
453            $crate::seed::FakeSeeder {
454                model: $name,
455                run: |__pool, __count| ::std::boxed::Box::pin(async move {
456                    <$model>::factory().fake().create_many(__count, __pool).await.len()
457                }),
458            }
459        }
460    };
461}
462
463/// No-op fake-seeder registration (internal): emitted when autumn-web is built
464/// without the `seed` feature, so `#[model]` compiles unchanged.
465#[cfg(not(feature = "seed"))]
466#[macro_export]
467#[doc(hidden)]
468macro_rules! __autumn_register_fake_seeder {
469    ($model:ty, $name:expr) => {};
470}
471/// Widget story gallery (issue #1526).
472///
473/// Browsable `/_stories` UI plus a CI anti-rot registry of zero-arg widget
474/// render examples.
475#[cfg(feature = "maud")]
476pub mod stories;
477pub mod task;
478pub mod telemetry;
479pub mod ui;
480/// Active search and autocomplete form primitives with htmx integration.
481///
482/// See [`widgets`] for the full API including [`widgets::active_search`],
483/// [`widgets::autocomplete_input`], [`widgets::data_table`],
484/// [`widgets::property_list`], and their configuration types.
485pub mod widgets;
486/// First-class multi-step form wizards with session-backed state and per-step validation.
487///
488/// See [`wizard`] for the full API including [`wizard::WizardContext`] and
489/// [`wizard::wizard_progress`].
490pub mod wizard;
491/// Changeset type carrying submitted values + per-field errors.
492pub use form::Changeset;
493/// Changeset form extractor — decodes body + validates, captures errors in [`form::Changeset`].
494pub use form::ChangesetForm;
495/// Trait implemented for all `validator::Validate` types to produce a [`Changeset`].
496pub use form::IntoChangeset;
497#[cfg(feature = "maud")]
498pub use nested_form::{InputsForOptions, RowScope, inputs_for, nested_row_fragment};
499/// Nested (`has_many`) form binding: parent + one child collection.
500pub use nested_form::{
501    NestedChangeset, NestedChangesetForm, NestedChild, NestedRow, decode_nested_urlencoded,
502};
503pub mod data;
504pub mod normalize;
505pub mod validation;
506pub mod webhook;
507#[cfg(feature = "http-client")]
508pub mod webhook_outbound;
509#[cfg(feature = "ws")]
510pub mod ws;
511
512/// Private runtime helpers for code generated by Autumn proc macros.
513///
514/// This module is semver-exempt. Do not use it directly.
515#[doc(hidden)]
516pub mod __private {
517    #[cfg(feature = "db")]
518    pub use crate::db::scoped_immediate_transaction;
519    #[cfg(feature = "db")]
520    pub use crate::db::scoped_transaction;
521    #[cfg(all(feature = "db", feature = "ws"))]
522    pub use crate::repository_commit_hooks::CURRENT_CHANNELS;
523    #[cfg(feature = "db")]
524    pub use crate::repository_commit_hooks::{
525        RepositoryCommitHookDescriptor, catch_repository_after_hook_unwind,
526        discard_repository_commit_hook_pending, enqueue_repository_commit_hook_on_conn,
527        enqueue_repository_commit_hook_pending_on_conn,
528        enqueue_repository_commit_hooks_bulk_on_conn,
529        enqueue_repository_commit_hooks_pending_bulk_on_conn,
530        finalize_repository_commit_hook_after_hook, kick_repository_commit_hook_dispatcher,
531        mark_repository_commit_hook_after_hook_failed, register_repository_commit_hook_runner,
532        start_repository_commit_hook_pending_finalizer_heartbeat,
533        start_repository_commit_hook_worker,
534    };
535    #[cfg(feature = "db")]
536    pub use crate::repository_commit_hooks::{
537        clear_global_channels, get_global_channels, set_global_channels,
538    };
539    #[cfg(feature = "db")]
540    pub use crate::version_history::VersionedRepositoryDescriptor;
541
542    pub use crate::router::check_sunset;
543
544    // Shared factory creation depth — bounds cyclic `#[factory_assoc]` chains
545    // across all models in a single create() chain.
546    //
547    // Task-local (not thread-local) so it is maintained correctly when a
548    // generated async `create()` future migrates between worker threads on a
549    // work-stealing runtime such as Tokio's multi-thread scheduler.
550    tokio::task_local! {
551        pub static FACTORY_DEPTH: u32;
552    }
553}
554
555pub use crate::router::RouteVersionMetadata;
556
557/// Create a new [`app::AppBuilder`] for configuring and launching an Autumn server.
558///
559/// This is the primary entry point for every Autumn application.
560///
561/// # Examples
562///
563/// ```rust,no_run
564/// use autumn_web::prelude::*;
565///
566/// #[get("/")]
567/// async fn index() -> &'static str { "hello" }
568///
569/// #[autumn_web::main]
570/// async fn main() {
571///     autumn_web::app()
572///         .routes(routes![index])
573///         .run()
574///         .await;
575/// }
576/// ```
577pub use app::app;
578pub use app::{ApiVersion, RegisteredApiVersions};
579/// Async database connection extractor.
580///
581/// Declare `db: Db` in a handler signature to get a pooled Postgres
582/// connection. See [`db::Db`] for full documentation and examples.
583#[cfg(feature = "db")]
584pub use db::Db;
585
586/// Transaction options (isolation level + retry policy) and the savepoint
587/// helper for [`Db::tx_with`]. See [`db::TxOptions`].
588#[cfg(feature = "db")]
589pub use db::{IsolationLevel, TxOptions, savepoint};
590
591/// The runtime database connection type (Postgres by default; `SQLite` under the
592/// `sqlite` feature). Named by generated `#[repository]`/`#[model]` code as
593/// `::autumn_web::RuntimeConnection`. See [`db::RuntimeConnection`].
594#[cfg(feature = "db")]
595pub use db::RuntimeConnection;
596
597/// The runtime diesel query backend (`diesel::pg::Pg` by default;
598/// `diesel::sqlite::Sqlite` under the `sqlite` feature). Named by generated
599/// `#[repository]`/`#[model]` code as `::autumn_web::RuntimeBackend`. See
600/// [`db::RuntimeBackend`].
601#[cfg(feature = "db")]
602pub use db::RuntimeBackend;
603
604/// Framework error type and result alias.
605///
606/// [`AutumnError`] wraps any `Error + Send + Sync` with an HTTP status code.
607/// [`AutumnResult<T>`] is `Result<T, AutumnError>`.
608/// See the [`error`] module for details.
609pub use error::{AutumnError, AutumnResult};
610
611pub use tenant_cell::{QuotaExceeded, TenantCell, TenantCellHandle, TenantCellRegistry};
612
613/// Paginated list response wrapper with navigation metadata.
614///
615/// See the [`pagination`] module for the full query contract and usage
616/// patterns.
617pub use pagination::Page;
618
619/// Pagination parameters extracted from the query string.
620///
621/// See the [`pagination`] module for the full query contract and usage
622/// patterns.
623pub use pagination::PageRequest;
624
625/// Allowlisted sort/filter parameters extracted from the query string, and the
626/// canonical [`SortDir`] direction. Compose with [`PageRequest`] to drive the
627/// `#[repository]`-generated `list()` method.
628///
629/// See the [`pagination`] module for the security model (the allowlist is the
630/// injection boundary) and the query contract.
631pub use pagination::{ListQuery, SortDir};
632
633/// Cursor pagination response wrapper. Companion to [`CursorRequest`]
634/// for keyset/seek pagination of real-time feeds.
635///
636/// See the [`pagination`] module for the full query contract and usage
637/// patterns.
638pub use pagination::CursorPage;
639
640/// Eager-loaded record wrapper and the typed `NotLoaded` accessor error.
641///
642/// See the [`preload`] module for declaring `#[belongs_to]` / `#[has_many]` /
643/// `#[has_one]` associations and loading them with a `#[repository]`
644/// `preload(...)` call.
645pub use preload::{NotLoaded, Preloaded};
646
647/// Cursor pagination parameters extracted from the query string.
648///
649/// See the [`pagination`] module for the full query contract and usage
650/// patterns.
651pub use pagination::CursorRequest;
652
653/// Auto-validating extractor. Wraps `Json<T>`, `Form<T>`, or `Query<T>`
654/// and validates via `validator::Validate` before the handler runs.
655/// Returns 422 with structured error details on validation failure.
656pub use validation::Valid;
657
658/// Proof that `T` has passed validation. See [`validation`] module.
659pub use validation::Validated;
660
661/// htmx version string embedded in the binary.
662///
663/// Useful for cache-busting or diagnostic logging. The corresponding
664/// minified JS is served automatically at `/static/js/htmx.min.js`.
665#[cfg(feature = "htmx")]
666pub use htmx::{
667    AUTUMN_WIDGETS_JS_PATH, HTMX_CSRF_JS_PATH, HTMX_JS, HTMX_JS_PATH, HTMX_SSE_JS,
668    HTMX_SSE_JS_PATH, HTMX_VERSION, IDIOMORPH_JS, IDIOMORPH_JS_PATH,
669};
670#[cfg(all(feature = "htmx", feature = "maud"))]
671pub use htmx::{HtmxFragments, OobSwap};
672/// Trait for rendering a model instance as an htmx `hx-swap-oob` fragment.
673///
674/// Implement this on your model and declare `broadcasts = "topic"` on the
675/// `#[repository]` attribute to enable automatic live broadcasts.
676#[cfg(all(feature = "htmx", feature = "maud"))]
677pub use live::LiveFragment;
678#[cfg(feature = "mail")]
679pub use mail::{
680    Mail, MailAttachment, MailConfig, MailDeliveryQueue, MailDeliveryQueueHandle, MailError,
681    MailTransport, Mailer, SmtpConfig, TlsMode, Transport,
682};
683/// Extension trait adding `.validate()` to all `validator::Validate` types.
684pub use validation::ValidateExt;
685
686// ── Proc-macro re-exports ──────────────────────────────────────────
687
688/// Annotate an async function as a `DELETE` route handler.
689///
690/// Generates a companion function that returns a [`crate::route::Route`]
691/// pairing the path with an Axum handler. In debug builds
692/// `#[axum::debug_handler]` is applied automatically for better error
693/// messages (zero cost in release).
694///
695/// # Examples
696///
697/// ```rust,no_run
698/// use autumn_web::prelude::*;
699///
700/// #[delete("/items/{id}")]
701/// async fn remove_item() -> &'static str {
702///     "removed"
703/// }
704/// ```
705pub use autumn_macros::delete;
706
707/// Enrich a route handler's auto-generated `OpenAPI` documentation.
708///
709/// See the [`openapi`] module and the [`autumn_macros::api_doc`]
710/// attribute docs for details on the supported keys.
711///
712/// # Example
713///
714/// ```rust,no_run
715/// use autumn_web::prelude::*;
716///
717/// #[get("/users/{id}")]
718/// #[api_doc(summary = "Fetch a user by id", tag = "users")]
719/// async fn get_user(Path(id): Path<i32>) -> String {
720///     format!("User {id}")
721/// }
722/// ```
723pub use autumn_macros::api_doc;
724
725/// Annotate an async function as a `GET` route handler.
726///
727/// Generates a companion function that returns a [`crate::route::Route`]
728/// pairing the path with an Axum handler. In debug builds
729/// `#[axum::debug_handler]` is applied automatically for better error
730/// messages (zero cost in release).
731///
732/// # Examples
733///
734/// ```rust,no_run
735/// use autumn_web::prelude::*;
736///
737/// #[get("/hello")]
738/// async fn hello() -> &'static str {
739///     "Hello, Autumn!"
740/// }
741/// ```
742pub use autumn_macros::get;
743/// Annotate an async function as a first-class inbound mail handler.
744///
745/// See [`inbound_mail`] for usage documentation.
746#[cfg(feature = "inbound-mail")]
747pub use autumn_macros::inbound_mail;
748/// Collect mailer preview registrations into an `AppBuilder`.
749#[cfg(feature = "mail")]
750pub use autumn_macros::mail_previews;
751/// Generate ergonomic `send_*` and `deliver_later_*` helpers for mailer impls.
752#[cfg(feature = "mail")]
753pub use autumn_macros::mailer;
754/// Register zero-argument mail template previews for the dev mail UI.
755#[cfg(feature = "mail")]
756pub use autumn_macros::mailer_preview;
757/// Set up the Tokio async runtime for an Autumn application.
758///
759/// A thin wrapper around `#[tokio::main]`. The real framework setup
760/// happens inside [`app::AppBuilder::run`].
761///
762/// # Examples
763///
764/// ```rust,no_run
765/// use autumn_web::prelude::*;
766///
767/// #[get("/")]
768/// async fn index() -> &'static str { "hi" }
769///
770/// #[autumn_web::main]
771/// async fn main() {
772///     autumn_web::app()
773///         .routes(routes![index])
774///         .run()
775///         .await;
776/// }
777/// ```
778pub use autumn_macros::main;
779/// Author a widget story for the `/_stories` gallery:
780/// `story!{ "Group", "Name", { ... } }`.
781///
782/// Also available as [`stories::story`], the macro's module home.
783#[cfg(feature = "maud")]
784pub use autumn_macros::story;
785
786/// Derive Diesel and Serde traits for a database model struct.
787///
788/// Applies `Queryable`, `Selectable`, `Insertable`, `Serialize`, and
789/// `Deserialize` derives plus a `#[diesel(table_name = ...)]` attribute.
790/// The table name is either specified explicitly or inferred from the
791/// struct name (`PascalCase` -> `snake_case` + `s`).
792///
793/// # Examples
794///
795/// Explicit table name:
796///
797/// ```rust,ignore
798/// use autumn_web::model;
799///
800/// #[model(table = "users")]
801/// pub struct User {
802///     pub id: i64,
803///     pub name: String,
804/// }
805/// ```
806///
807/// Inferred table name (`BlogPost` -> `blog_posts`):
808///
809/// ```rust,ignore
810/// use autumn_web::model;
811///
812/// #[model]
813/// pub struct BlogPost {
814///     pub id: i64,
815///     pub title: String,
816/// }
817/// ```
818#[cfg(feature = "db")]
819pub use autumn_macros::model;
820/// Annotate an OAuth2/OIDC callback handler.
821///
822/// Convenience alias for `#[get(...)]` with callback-focused naming.
823pub use autumn_macros::oauth2_callback;
824
825/// Derive a repository with CRUD operations and derived queries.
826///
827/// See [`macro@repository`] for details.
828#[cfg(feature = "db")]
829pub use autumn_macros::repository;
830
831/// Define a service for cross-model orchestration and non-DB side effects.
832///
833/// Generates a `XxxServiceImpl` struct with dependency injection.
834/// Use when logic spans multiple repositories or involves non-DB work.
835/// For single-model CRUD, use [`macro@repository`] instead.
836///
837/// # Examples
838///
839/// ```rust,ignore
840/// use autumn_web::service;
841///
842/// #[service]
843/// pub trait OrderService {
844///     fn deps(order_repo: PgOrderRepository, inventory_repo: PgInventoryRepository);
845/// }
846///
847/// impl OrderServiceImpl {
848///     pub async fn place_order(&self, req: PlaceOrderRequest) -> AutumnResult<Order> {
849///         let order = self.order_repo.save(&req.into()).await?;
850///         self.inventory_repo.reserve(order.id).await?;
851///         Ok(order)
852///     }
853/// }
854/// ```
855#[cfg(feature = "db")]
856pub use autumn_macros::service;
857
858/// Annotate an async function as a `PATCH` route handler.
859///
860/// Generates a companion function that returns a [`crate::route::Route`]
861/// and a typed `__autumn_path_{name}(…) -> String` path helper.
862///
863/// # Examples
864///
865/// ```rust,no_run
866/// use autumn_web::patch;
867///
868/// #[patch("/items/{id}")]
869/// async fn patch_item() -> &'static str {
870///     "patched"
871/// }
872/// ```
873pub use autumn_macros::patch;
874
875/// Emit a `pub mod paths { … }` re-exporting typed path helpers.
876///
877/// Takes the same comma-separated handler list as [`routes!`]. Invoke once
878/// in the module where your handlers live:
879///
880/// ```ignore
881/// autumn_web::paths![show_post, create_post];
882/// // callers can then: use crate::routes::paths;
883/// //                    paths::show_post(42)
884/// ```
885pub use autumn_macros::paths;
886
887/// HTTP redirect response.
888///
889/// Re-exported from [Axum](https://docs.rs/axum) so route handlers can
890/// return a redirect without a direct `axum` dependency.
891///
892/// Use [`Redirect::to`] with a path helper:
893///
894/// ```ignore
895/// use autumn_web::Redirect;
896/// Redirect::to(&paths::show_post(id))
897/// ```
898pub use axum::response::Redirect;
899
900/// Annotate an async function as a `POST` route handler.
901///
902/// Generates a companion function that returns a [`crate::route::Route`]
903/// pairing the path with an Axum handler. In debug builds
904/// `#[axum::debug_handler]` is applied automatically for better error
905/// messages (zero cost in release).
906///
907/// # Examples
908///
909/// ```rust,no_run
910/// use autumn_web::prelude::*;
911///
912/// #[post("/items")]
913/// async fn create_item() -> &'static str {
914///     "created"
915/// }
916/// ```
917pub use autumn_macros::post;
918
919/// Annotate an async function as a `PUT` route handler.
920///
921/// Generates a companion function that returns a [`crate::route::Route`]
922/// pairing the path with an Axum handler. In debug builds
923/// `#[axum::debug_handler]` is applied automatically for better error
924/// messages (zero cost in release).
925///
926/// # Examples
927///
928/// ```rust,no_run
929/// use autumn_web::prelude::*;
930///
931/// #[put("/items/{id}")]
932/// async fn update_item() -> &'static str {
933///     "updated"
934/// }
935/// ```
936pub use autumn_macros::put;
937
938/// Collect route-annotated handlers into a `Vec<Route>`.
939///
940/// Each handler must have been annotated with a route macro ([`get`],
941/// [`post`], [`put`], [`delete`]) which generates a companion
942/// `__autumn_route_info_{name}()` function.
943///
944/// # Examples
945///
946/// ```rust,no_run
947/// use autumn_web::prelude::*;
948///
949/// #[get("/hello")]
950/// async fn hello() -> &'static str { "hello" }
951///
952/// #[post("/create")]
953/// async fn create() -> &'static str { "created" }
954///
955/// # #[autumn_web::main]
956/// # async fn main() {
957/// let all_routes = routes![hello, create];
958/// autumn_web::app().routes(all_routes).run().await;
959/// # }
960/// ```
961pub use autumn_macros::routes;
962
963/// Cache the return value of a function based on its arguments.
964///
965/// Wraps a function with an in-memory cache backed by a static
966/// [`MokaCache`](cache::MokaCache) (default) via the [`Cache`](cache::Cache)
967/// trait. Arguments must implement `Hash + Clone`; the return type must
968/// be `Clone + Send + Sync + 'static`.
969///
970/// Use `result` to only cache `Ok` values from `Result`-returning
971/// functions (common with [`AutumnResult`]).
972///
973/// # Examples
974///
975/// ```rust,ignore
976/// use autumn_web::cached;
977///
978/// #[cached(ttl = "5m", max = 100, result)]
979/// async fn get_user(id: i64) -> AutumnResult<User> {
980///     db.find(id).await
981/// }
982/// ```
983pub use autumn_macros::cached;
984
985/// Annotate an async function as a WebSocket route handler.
986///
987/// The function follows the **two-function pattern**: it runs at HTTP
988/// upgrade time and returns a closure implementing [`ws::WsHandler`]
989/// that handles the live WebSocket connection.
990///
991/// Generates a GET route for the WebSocket upgrade, compatible with
992/// [`routes!`]. Requires the `ws` feature.
993///
994/// # Examples
995///
996/// ```rust,ignore
997/// use autumn_web::prelude::*;
998/// use autumn_web::ws::{WebSocket, Message, WsHandler};
999///
1000/// #[ws("/echo")]
1001/// async fn echo() -> impl WsHandler {
1002///     |mut socket: WebSocket| async move {
1003///         while let Some(Ok(msg)) = socket.recv().await {
1004///             if let Message::Text(text) = msg {
1005///                 socket.send(Message::Text(text)).await.ok();
1006///             }
1007///         }
1008///     }
1009/// }
1010/// ```
1011#[cfg(feature = "ws")]
1012pub use autumn_macros::ws;
1013
1014/// Declare a typed domain event. See [`mod@events`] module.
1015pub use autumn_macros::event;
1016/// Declare an on-demand background job. See [`mod@job`] module.
1017pub use autumn_macros::job;
1018/// Declare an event listener. See [`mod@events`] module.
1019pub use autumn_macros::listener;
1020/// Declare a scheduled background task. See [`mod@task`] module.
1021pub use autumn_macros::scheduled;
1022/// Declare a one-off operational task. See [`task::OneOffTaskInfo`].
1023pub use autumn_macros::task;
1024
1025/// Extractor that yields a verified bearer-token principal for API routes.
1026///
1027/// Must be used with [`auth::RequireApiToken`] middleware. See the
1028/// [`auth`] module for a complete quick-start example.
1029pub use auth::ApiToken;
1030
1031/// Tower layer that validates `Authorization: Bearer <token>` on API routes.
1032///
1033/// Verifies tokens against any [`auth::ApiTokenStore`] implementation.
1034/// Returns `401 Unauthorized` for missing, unknown, revoked, or expired tokens.
1035pub use auth::RequireApiToken;
1036
1037/// Scoped service-token types and helpers: mint named, scoped, optionally
1038/// expiring tokens whose granted scopes flow into the policy layer.
1039pub use auth::{
1040    ApiTokenScopes, IssueTokenSpec, TokenMetadata, VerifiedToken, issue_scoped_api_token,
1041    list_api_tokens, rotate_api_token,
1042};
1043
1044/// Postgres-backed API token store (requires `db` feature).
1045///
1046/// Production replacement for [`auth::InMemoryApiTokenStore`]. Hashes tokens
1047/// at rest and persists them across restarts. Use with [`API_TOKEN_MIGRATIONS`]
1048/// for dev/test startup checks; `autumn migrate` applies the token-table
1049/// framework migration in production.
1050#[cfg(feature = "db")]
1051pub use auth::DbApiTokenStore;
1052
1053/// Embedded Diesel migrations for the `api_tokens` table (requires `db` feature).
1054///
1055/// Pass to `app().migrations()` so that dev/test startup migration checks can
1056/// create and validate the `api_tokens` table alongside your application
1057/// migrations.
1058#[cfg(feature = "db")]
1059pub use auth::API_TOKEN_MIGRATIONS;
1060
1061/// Secure a route handler with authentication and optional role checks.
1062///
1063/// Applied before a route macro (`#[get]`, `#[post]`, etc.), this attribute
1064/// injects an authentication guard at the top of the handler. The guard
1065/// checks the session for the configured auth key (default: `"user_id"`)
1066/// and, when roles are specified, verifies the user's role matches.
1067///
1068/// Returns `401 Unauthorized` if not authenticated, or `403 Forbidden`
1069/// if the user lacks the required role.
1070///
1071/// The handler must return [`AutumnResult<T>`] so the guard can use `?`
1072/// to short-circuit on failure.
1073///
1074/// # Forms
1075///
1076/// - `#[secured]` -- require authentication only
1077/// - `#[secured("admin")]` -- require a specific role
1078/// - `#[secured("admin", "editor")]` -- require any of the listed roles
1079///
1080/// # Examples
1081///
1082/// ```rust,no_run
1083/// use autumn_web::prelude::*;
1084///
1085/// #[get("/dashboard")]
1086/// #[secured]
1087/// async fn dashboard() -> AutumnResult<&'static str> {
1088///     Ok("welcome")
1089/// }
1090///
1091/// #[get("/admin")]
1092/// #[secured("admin")]
1093/// async fn admin_panel() -> AutumnResult<&'static str> {
1094///     Ok("admin area")
1095/// }
1096///
1097/// #[get("/content")]
1098/// #[secured("admin", "editor")]
1099/// async fn manage_content() -> AutumnResult<&'static str> {
1100///     Ok("content manager")
1101/// }
1102/// ```
1103pub use autumn_macros::secured;
1104
1105/// Declare a route handler as deliberately public (unauthenticated).
1106///
1107/// A compile-time marker that records intent: it injects no runtime guard and
1108/// leaves the handler signature untouched, but surfaces on the route's
1109/// [`ApiDoc::public`](crate::openapi::ApiDoc::public) so the build-time security
1110/// classifier (`autumn routes audit`) treats the route as an explicit opt-out
1111/// of authentication rather than an oversight.
1112///
1113/// # Example
1114///
1115/// ```rust,no_run
1116/// use autumn_web::prelude::*;
1117///
1118/// #[get("/pricing")]
1119/// #[public]
1120/// async fn pricing() -> &'static str { "free" }
1121/// ```
1122pub use autumn_macros::public;
1123
1124/// Require fresh ("step-up") authentication before a route handler runs.
1125///
1126/// The handler is guarded by a freshness check on the session's
1127/// `last_strong_auth_at` claim. When the claim is missing or older than
1128/// `max_age` (default: 5 minutes) the request is handled as follows:
1129///
1130/// - **Browser clients**: redirect to `/reauth?return_to=<current-path>`.
1131/// - **API / JSON clients** (`Accept: application/json`): `401` with
1132///   RFC 7807 problem-details and `WWW-Authenticate: StepUp max-age=N`.
1133///
1134/// # Examples
1135///
1136/// ```rust,ignore
1137/// use autumn_web::prelude::*;
1138///
1139/// // Default 5-minute window.
1140/// #[delete("/account")]
1141/// #[step_up]
1142/// async fn destroy_account() -> AutumnResult<Redirect> {
1143///     Ok(Redirect::to("/bye"))
1144/// }
1145///
1146/// // Custom window.
1147/// #[post("/auth/mfa/remove")]
1148/// #[step_up(max_age = "2m")]
1149/// async fn remove_mfa() -> AutumnResult<&'static str> {
1150///     Ok("removed")
1151/// }
1152/// ```
1153pub use autumn_macros::step_up;
1154
1155/// Apply a per-route rate limit that composes with the global limiter.
1156///
1157/// # Forms
1158///
1159/// - `#[throttle(limit = 5, per = "1m")]` — inline limit; keying strategy
1160///   matches the global limiter (`[security.rate_limit]`).
1161/// - `#[throttle(limit = 5, per = "1m", key = "ip" | "principal" | "token")]`
1162///   — inline limit with an explicit key strategy.
1163/// - `#[throttle("login")]` — reference a named limiter defined in
1164///   `[security.rate_limit.named.login]`.
1165///
1166/// Requests denied by the per-route bucket receive `429 Too Many Requests`
1167/// with a `Retry-After` header and the standard `x-ratelimit-*` headers.
1168/// [`RateLimitExempt`](crate::security::RateLimitExempt) still bypasses the
1169/// per-route throttle.
1170///
1171/// # Example
1172///
1173/// ```rust,ignore
1174/// use autumn_web::prelude::*;
1175///
1176/// #[post("/login")]
1177/// #[throttle(limit = 5, per = "1m", key = "ip")]
1178/// async fn login() -> AutumnResult<&'static str> {
1179///     Ok("welcome back")
1180/// }
1181/// ```
1182pub use autumn_macros::throttle;
1183
1184/// Gate a route handler on a named feature flag. If the flag is disabled for
1185/// the current actor the handler responds with `404 Not Found` (default) or
1186/// delegates to a custom fallback specified with `fallback = my_fn`.
1187///
1188/// Requires a [`FeatureFlagService`](crate::feature_flags::FeatureFlagService)
1189/// installed in the app's [`AppState`] extensions.
1190///
1191/// # Example
1192///
1193/// ```rust,ignore
1194/// use autumn_web::prelude::*;
1195///
1196/// #[get("/beta")]
1197/// #[feature_flag("beta_dashboard")]
1198/// async fn beta_dashboard() -> Markup {
1199///     html! { h1 { "Beta!" } }
1200/// }
1201/// ```
1202pub use autumn_macros::feature_flag;
1203
1204/// Enforce a record-level [`Policy`](crate::authorization::Policy)
1205/// before a handler runs. Coexists with [`secured`](macro@secured):
1206/// `#[secured]` answers "are you in?", `#[authorize]` answers
1207/// "are you allowed to act on *this record*?"
1208///
1209/// # Examples
1210///
1211/// ```rust,ignore
1212/// use autumn_web::prelude::*;
1213///
1214/// #[get("/posts/{id}/edit")]
1215/// #[authorize("update", resource = Post)]
1216/// async fn edit_post(post: Post) -> AutumnResult<Markup> {
1217///     Ok(html! { h1 { (post.title) } })
1218/// }
1219/// ```
1220pub use autumn_macros::authorize;
1221/// Collect `#[job]` handlers into a `Vec<JobInfo>`.
1222pub use autumn_macros::jobs;
1223/// Collect `#[listener]` handlers into a `Vec<events::ListenerInfo>`.
1224pub use autumn_macros::listeners;
1225
1226/// Collect `#[task]` handlers into a `Vec<task::OneOffTaskInfo>`.
1227pub use autumn_macros::one_off_tasks;
1228
1229/// Collect `#[scheduled]` task handlers into a `Vec<TaskInfo>`.
1230pub use autumn_macros::tasks;
1231
1232/// Collect `#[static_get]` handlers into a `Vec<StaticRouteMeta>`.
1233pub use autumn_macros::static_routes;
1234
1235/// Annotate an async function as a statically pre-rendered GET route.
1236///
1237/// Like [`get`], this generates a route companion for Axum routing.
1238/// Additionally, it emits a `__autumn_static_meta_{name}()` companion
1239/// that registers the route for static HTML generation at build time
1240/// (`autumn build`).
1241///
1242/// Phase 1 restriction: path parameters (`{id}`) are **not** supported.
1243/// Use [`get`] for parameterized routes.
1244///
1245/// # Examples
1246///
1247/// ```rust,no_run
1248/// use autumn_web::prelude::*;
1249///
1250/// #[static_get("/about")]
1251/// async fn about() -> &'static str {
1252///     "About us"
1253/// }
1254/// ```
1255pub use autumn_macros::static_get;
1256
1257/// Turn a plain state enum into a statically-verified lifecycle.
1258///
1259/// Applied to an enum with an `initial` state, one or more `terminal` states,
1260/// and a set of `transitions`, this preserves the original enum and appends
1261/// metadata consts (`LIFECYCLE_INITIAL`, `LIFECYCLE_TERMINALS`,
1262/// `LIFECYCLE_STATES`, `LIFECYCLE_TRANSITIONS`) plus `can_transition_to` on the
1263/// enum, and a typestate transition module (named after the enum in
1264/// `snake_case`) whose `Machine<S>` only exposes `to_<target>` methods for
1265/// declared edges — firing an undeclared transition is a compile error.
1266///
1267/// # Examples
1268///
1269/// ```rust,ignore
1270/// use autumn_web::lifecycle;
1271///
1272/// #[lifecycle(
1273///     initial = Draft,
1274///     terminal(Archived),
1275///     transitions(
1276///         Draft -> Published,
1277///         Published -> Archived,
1278///         Published -> Draft,
1279///     )
1280/// )]
1281/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
1282/// pub enum ArticleState { Draft, Published, Archived }
1283/// ```
1284pub use autumn_macros::lifecycle;
1285
1286/// Marker trait implemented by every `#[lifecycle]` enum, exposing that
1287/// lifecycle's transition edges as a string-keyed table.
1288///
1289/// This is the bridge that lets a field-level `#[state_machine(lifecycle = X)]`
1290/// on a `#[model]` derive its runtime transitions table from a `#[lifecycle]`
1291/// enum `X` instead of an inline `transitions(...)` list — "transitions defined
1292/// once, typed" (issue #1911). The `#[lifecycle]` macro is the *only* thing that
1293/// implements this trait; referencing a type that is not a `#[lifecycle]` enum in
1294/// `#[state_machine(lifecycle = ...)]` therefore fails to compile with an
1295/// unsatisfied `T: Lifecycle` trait bound rather than a cryptic
1296/// "no associated const" error.
1297///
1298/// [`STATE_MACHINE_TRANSITIONS`](Lifecycle::STATE_MACHINE_TRANSITIONS) has the
1299/// exact `(from, to, guard)` shape the field-level `#[state_machine]` inline
1300/// table uses, so a lifecycle-derived state machine is byte-for-byte the same
1301/// runtime construct as the equivalent inline one. Lifecycle transitions carry
1302/// no guards, so every `guard` slot is `None` (see the `#[state_machine]` docs
1303/// for the guards rationale).
1304pub trait Lifecycle {
1305    /// This lifecycle's declared transition edges as
1306    /// `(from_variant_name, to_variant_name, guard)` triples, where the variant
1307    /// names are the enum variants rendered as strings (matching the value
1308    /// stored in the model's `String` column). The `guard` slot is always
1309    /// `None` — lifecycle transitions are unguarded.
1310    const STATE_MACHINE_TRANSITIONS: &'static [(
1311        &'static str,
1312        &'static str,
1313        ::core::option::Option<&'static str>,
1314    )];
1315}
1316
1317/// Context payload delivered to an `on_commit` transition-effect job
1318/// (issue #1973).
1319///
1320/// When a `#[state_machine]` edge declares `on_commit = SomeJob`, firing that
1321/// edge via the generated `transition_{field}_to_on_conn` method enqueues
1322/// `SomeJob` **transactionally** on the caller's connection with an instance of
1323/// this struct as its payload. Because the enqueue writes the job row inside the
1324/// caller's own transaction, a rollback drops the effect; the durable worker
1325/// runs it post-commit with full `AppState` (at-least-once delivery).
1326///
1327/// Declare the job to receive it, deduping on the derived key so a retried
1328/// transition coalesces into a single delivery:
1329///
1330/// ```rust,ignore
1331/// #[job(name = "send_shipped_email", unique_by = "idempotency_key")]
1332/// async fn send_shipped_email(
1333///     state: AppState,
1334///     effect: TransitionEffect,
1335/// ) -> AutumnResult<()> {
1336///     // effect.model / .field / .record_id / .from_state / .to_state
1337///     Ok(())
1338/// }
1339/// ```
1340///
1341/// The [`idempotency_key`](TransitionEffect::idempotency_key) is derived from
1342/// `(model, field, record_id, from_state, to_state)`, so declaring the job
1343/// `unique_by = "idempotency_key"` gives idempotent, coalescing delivery.
1344#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1345pub struct TransitionEffect {
1346    /// The model type name whose field transitioned (e.g. `"Order"`).
1347    pub model: String,
1348    /// The state-machine field name that transitioned (e.g. `"status"`).
1349    pub field: String,
1350    /// The record's primary-key value, rendered as a string.
1351    pub record_id: String,
1352    /// The state the field moved from.
1353    pub from_state: String,
1354    /// The state the field moved to.
1355    pub to_state: String,
1356    /// Derived dedup key:
1357    /// `"{model}:{field}:{record_id}:{from_state}:{to_state}"`.
1358    pub idempotency_key: String,
1359}
1360
1361/// Internal: returns `true` if `(from, to)` appears as an edge in a
1362/// `#[lifecycle]` enum's `STATE_MACHINE_TRANSITIONS` table. Used by
1363/// `#[state_machine(lifecycle = ..., effects(...))]` codegen to reject at
1364/// compile time an effect declared on an edge the lifecycle does not permit
1365/// (which would otherwise silently drop the effect). Not part of the public API.
1366#[doc(hidden)]
1367#[must_use]
1368pub const fn __transition_edge_declared(
1369    table: &[(&str, &str, ::core::option::Option<&str>)],
1370    from: &str,
1371    to: &str,
1372) -> bool {
1373    // Iterators/`for` are not permitted in a const fn, so index with `while`.
1374    #[allow(clippy::needless_range_loop)]
1375    let mut i = 0;
1376    while i < table.len() {
1377        let (f, t, _) = table[i];
1378        if __const_str_eq(f, from) && __const_str_eq(t, to) {
1379            return true;
1380        }
1381        i += 1;
1382    }
1383    false
1384}
1385
1386/// Internal: byte-wise `&str` equality usable in a const context (where the
1387/// `PartialEq` `==` operator on `str` is not available). Not part of the
1388/// public API.
1389#[doc(hidden)]
1390#[must_use]
1391pub const fn __const_str_eq(a: &str, b: &str) -> bool {
1392    let (a, b) = (a.as_bytes(), b.as_bytes());
1393    if a.len() != b.len() {
1394        return false;
1395    }
1396    // Iterators/`for` are not permitted in a const fn, so index with `while`.
1397    #[allow(clippy::needless_range_loop)]
1398    let mut i = 0;
1399    while i < a.len() {
1400        if a[i] != b[i] {
1401            return false;
1402        }
1403        i += 1;
1404    }
1405    true
1406}
1407
1408// ── Maud re-exports ────────────────────────────────────────────────
1409
1410/// Rendered HTML fragment produced by the [`html!`] macro.
1411///
1412/// This is the standard return type for handlers that render HTML.
1413/// Re-exported from [Maud](https://maud.lambda.xyz).
1414///
1415/// # Examples
1416///
1417/// ```rust,no_run
1418/// use autumn_web::prelude::*;
1419///
1420/// #[get("/")]
1421/// async fn index() -> Markup {
1422///     html! { h1 { "Welcome" } }
1423/// }
1424/// ```
1425#[cfg(feature = "maud")]
1426pub use maud::Markup;
1427
1428/// Wrap a pre-escaped string so Maud renders it verbatim.
1429///
1430/// Use this when you have HTML that was already escaped or generated
1431/// by another system and you want to embed it in a Maud template
1432/// without double-escaping.
1433///
1434/// Re-exported from [Maud](https://maud.lambda.xyz).
1435///
1436/// # Examples
1437///
1438/// ```rust
1439/// use autumn_web::PreEscaped;
1440///
1441/// let raw_html = PreEscaped("<em>already escaped</em>".to_string());
1442/// ```
1443#[cfg(feature = "maud")]
1444pub use maud::PreEscaped;
1445
1446/// Type-safe HTML templating macro.
1447///
1448/// Produces a [`Markup`] value containing compiled HTML.
1449/// Re-exported from [Maud](https://maud.lambda.xyz). See the
1450/// [Maud book](https://maud.lambda.xyz) for full syntax reference.
1451///
1452/// # Examples
1453///
1454/// ```rust
1455/// use autumn_web::html;
1456///
1457/// let greeting = "world";
1458/// let page = html! {
1459///     h1 { "Hello, " (greeting) "!" }
1460/// };
1461/// ```
1462#[cfg(feature = "maud")]
1463pub use maud::html;
1464
1465/// JSON request body extractor and response type.
1466///
1467/// When used as a handler parameter, deserializes the request body as JSON.
1468/// When returned from a handler, serializes the value as JSON with
1469/// `Content-Type: application/json`.
1470///
1471/// Wraps [Axum](https://docs.rs/axum)'s JSON extractor so parse failures use
1472/// Autumn's Problem Details error contract.
1473///
1474/// # Examples
1475///
1476/// ```rust,no_run
1477/// use autumn_web::prelude::*;
1478/// use serde::{Deserialize, Serialize};
1479///
1480/// #[derive(Deserialize)]
1481/// struct CreateItem { name: String }
1482///
1483/// #[derive(Serialize)]
1484/// struct Item { id: i64, name: String }
1485///
1486/// #[post("/items")]
1487/// async fn create(Json(input): Json<CreateItem>) -> Json<Item> {
1488///     Json(Item { id: 1, name: input.name })
1489/// }
1490/// ```
1491pub use crate::extract::Json;
1492
1493/// Path extractor.
1494///
1495/// Extract typed path parameters from the URL.
1496///
1497/// Wraps [Axum](https://docs.rs/axum)'s path extractor so parse failures use
1498/// Autumn's Problem Details error contract.
1499///
1500/// # Examples
1501///
1502/// ```rust,no_run
1503/// use autumn_web::prelude::*;
1504///
1505/// #[get("/users/{id}")]
1506/// async fn get_user(Path(id): Path<i32>) -> String {
1507///     format!("User {id}")
1508/// }
1509/// ```
1510pub use crate::extract::Path;
1511
1512/// Form data extractor.
1513pub use crate::extract::Form;
1514
1515/// Query extractor.
1516pub use crate::extract::Query;
1517
1518/// Resolved client IP address after trusted-proxy evaluation.
1519pub use crate::extract::ClientAddr;
1520
1521/// Resolved external host after trusted-proxy evaluation.
1522pub use crate::extract::ClientHost;
1523
1524/// Resolved external scheme (`"http"` / `"https"`) after trusted-proxy evaluation.
1525pub use crate::extract::ClientScheme;
1526
1527/// State extractor.
1528/// Re-exported from [Axum](https://docs.rs/axum).
1529pub use axum::extract::State;
1530
1531/// Re-exports of upstream crates used in macro-generated code.
1532///
1533/// These are public so that code generated by `autumn-macros` can reference
1534/// them as `autumn_web::reexports::axum`, etc. without requiring the user to
1535/// add those crates as direct dependencies.
1536///
1537/// **For advanced use cases only.** Prefer the types re-exported in
1538/// [`prelude`] or at the crate root. Reach into `reexports` when you
1539/// need direct access to the underlying framework types (e.g.,
1540/// `autumn_web::reexports::axum::Router` for custom middleware).
1541///
1542/// # Available crates
1543///
1544/// | Crate | Re-exported as | Use case |
1545/// |-------|---------------|----------|
1546/// | `axum` | `autumn_web::reexports::axum` | Custom routers, middleware, extractors |
1547/// | `diesel` | `autumn_web::reexports::diesel` | Raw Diesel queries, schema types |
1548/// | `http` | `autumn_web::reexports::http` | HTTP types (`StatusCode`, `Method`, headers) |
1549/// | `serde_json` | `autumn_web::reexports::serde_json` | JSON values and conversion helpers |
1550/// | `tokio` | `autumn_web::reexports::tokio` | Async runtime, spawn, timers |
1551pub mod reexports {
1552    pub use axum;
1553    pub use chrono;
1554    #[cfg(feature = "db")]
1555    pub use diesel;
1556    #[cfg(feature = "db")]
1557    pub use diesel_async;
1558    pub use http;
1559    pub use inventory;
1560    #[cfg(feature = "mail")]
1561    pub use lettre;
1562    pub use rust_decimal;
1563    #[cfg(feature = "db")]
1564    pub use scoped_futures;
1565    pub use serde;
1566    pub use serde_json;
1567    pub use tokio;
1568    pub use tokio_util;
1569    pub use tracing;
1570    pub use validator;
1571}
1572
1573/// Shared application state passed to route handlers.
1574pub(crate) mod state;
1575#[cfg(feature = "system-tests")]
1576pub mod system_test;
1577#[allow(
1578    clippy::missing_panics_doc,
1579    clippy::must_use_candidate,
1580    clippy::field_reassign_with_default
1581)]
1582pub mod test;
1583/// Dependency-free HTML parser + CSS-selector matcher backing the structural
1584/// HTML assertions on [`test::TestResponse`].
1585mod test_html;
1586pub use config::ProcessRole;
1587pub use state::AppState;
1588
1589#[cfg(test)]
1590mod tests {
1591    use super::*;
1592
1593    #[test]
1594    fn app_fn_creates_builder() {
1595        let builder = app::app();
1596        // Just verify it compiles and can accept routes
1597        let _builder = builder.routes(vec![]);
1598    }
1599}