pub struct AppBuilder { /* private fields */ }Expand description
Implementations§
Source§impl AppBuilder
impl AppBuilder
Sourcepub fn database(self, alias: &str, pool: impl Into<DbPool>) -> Self
pub fn database(self, alias: &str, pool: impl Into<DbPool>) -> Self
Register a database pool under the given alias.
The "default" pool is the one returned by umbral::db::pool()
and is required: build() fails with BuildError:: DefaultPoolMissing if it isn’t registered. The caller opens
the pool via umbral::db::connect(&url).await and passes it
here.
Accepts anything that converts into a DbPool: a typed
sqlx::SqlitePool, a typed sqlx::PgPool, or an already-
built DbPool. The From impls on DbPool make plain
SqlitePool callers (every test, every plugin example) work
unchanged.
Sourcepub fn router<R: DatabaseRouter + 'static>(self, router: R) -> Self
pub fn router<R: DatabaseRouter + 'static>(self, router: R) -> Self
Install a custom crate::db::DatabaseRouter. Omit to use
DefaultRouter (today’s static per-model routing).
Sourcepub fn route_context<F>(self, resolver: F) -> Self
pub fn route_context<F>(self, resolver: F) -> Self
Install a per-request crate::db::RouteContext resolver.
The resolver runs once per request, builds a RouteContext (typically
reading a tenant header or subdomain), and build() wraps the entire
downstream future in crate::db::route_context::scope. Because the
scope spans the whole handler — including every .await and every ORM
call — the ambient umbral::db::route_context() accessor inside the
handler, and the active crate::db::DatabaseRouter, see exactly the
context this resolver returned. A request the resolver maps to a
default RouteContext runs with no tenant (no silent inheritance from
a prior request).
use umbral::prelude::*;
use umbral::db::{RouteContext, TenantKey};
App::builder()
.route_context(|req| match req.headers().get("x-tenant") {
Some(v) => RouteContext::new()
.with_tenant(TenantKey::new(v.to_str().unwrap_or_default())),
None => RouteContext::new(),
})
.build()?;Sourcepub fn model<T: Model>(self) -> Self
pub fn model<T: Model>(self) -> Self
Register a model with the app’s migration engine.
Called once per model the user wants the M5 makemigrations /
migrate commands to track. Captures the model’s NAME /
TABLE / FIELDS constants into an owned ModelMeta so the
migration code can iterate without naming concrete T at the
call site. M7’s Plugin contract will replace this with
Plugin::models() discovered through the plugin registry.
Sourcepub fn plugin<P: Plugin>(self, plugin: P) -> Self
pub fn plugin<P: Plugin>(self, plugin: P) -> Self
Register a plugin (M7).
Plugins contribute models, routes, system_checks, and an
on_ready hook. App::build() topologically sorts the
registered set by Plugin::dependencies() and walks every
plugin’s contributions. The plugin name "app" is reserved
for the implicit plugin that owns models registered via
.model::<T>(); a plugin claiming that name causes
BuildError::ReservedPluginName.
Sourcepub fn routes(self, routes: Routes) -> Self
pub fn routes(self, routes: Routes) -> Self
Attach a Routes bundle of
hand-registered routes.
Each .get(...) / .post(...) / .put(...) / .patch(...) / .delete(...) / .head(...) / .options(...) call on Routes
records the path and registers the handler, so the framework
surfaces declared routes in the dev-mode 404 page without a
parallel declaration list.
Multi-method routes go through [Routes::route] (explicit
method list + axum::routing::MethodRouter). Routes that need
axum features the per-method shorthands don’t expose (typed
State, middleware layers, nest, fallback handlers, etc.)
go through [Routes::with_router] — that escape hatch merges
an external axum::Router and its paths stay opaque to the
framework (won’t appear in the dev 404 page).
Calling this more than once merges the router and concatenates the specs.
use umbral::prelude::*;
App::builder()
.routes(
Routes::new()
.get("/", home)
.get("/articles", list_articles_html)
.post("/api/articles", create_article),
)
.build()?;Sourcepub fn templates_dir<P: Into<PathBuf>>(self, path: P) -> Self
pub fn templates_dir<P: Into<PathBuf>>(self, path: P) -> Self
Set the project-level templates directory.
Defaults to ./templates (relative to the binary’s cwd) when
the builder method isn’t called. If the resolved path doesn’t
exist, the engine still publishes — calls to
umbral::templates::render then return TemplateError::Missing
with a clear diagnostic, which matches the “absence isn’t an
error unless something tries to render” rule from the spec.
This directory is searched first (highest priority). Plugin
directories contributed via Plugin::templates_dirs() are
appended in topological order and searched afterwards. To
override a plugin’s template, drop a same-named file here.
Sourcepub fn slash_redirect(self, policy: SlashRedirect) -> Self
pub fn slash_redirect(self, policy: SlashRedirect) -> Self
Set the trailing-slash redirect policy. See
crate::slash::SlashRedirect.
Default is Off (axum’s strict matching). Most apps want
Append (/foo 404 → 308 → /foo/) so that
the same URL works with or without the trailing slash.
use umbral::prelude::*;
use umbral::web::SlashRedirect;
App::builder()
.slash_redirect(SlashRedirect::Append)
.build()?;Sourcepub fn not_found_template(self, name: impl Into<String>) -> Self
pub fn not_found_template(self, name: impl Into<String>) -> Self
Set the template rendered on a 404. Follows the
404.html convention.
The template gets { path } in scope — the request path that
missed — so you can render The page {{ path }} doesn't exist. without wiring extractors. When unset, 404s return
plain-text “Not Found”. When set but the template fails to
render (missing file, parse error), the framework falls back
to the plain-text response and logs the render error.
Composes with Self::slash_redirect — if a slash-redirect
probe finds the alternate, it 308s before the not-found
template fires.
Sourcepub fn server_error_template(self, name: impl Into<String>) -> Self
pub fn server_error_template(self, name: impl Into<String>) -> Self
Set the template rendered on a panicking handler. Follows
the 500.html convention.
Installs a tower-http CatchPanic layer around the router.
A panic in any handler is caught, logged via tracing::error,
and replaced with a 500 response carrying the rendered
template. When unset, panics use tower-http’s default
behaviour (log + empty 500 body).
In dev mode (settings.environment == Dev), the template receives
dev_mode, error_display, error_chain, and request_path
context variables. In prod those variables are empty.
See Self::on_server_error for a hook that fires before the
template renders.
Sourcepub fn error_template(self, status: StatusCode, name: impl Into<String>) -> Self
pub fn error_template(self, status: StatusCode, name: impl Into<String>) -> Self
Register a custom template for error responses with status (e.g.
429, 403, 410). When a handler returns Err((status, message))
(or any non-HTML error response with this status), the template is
rendered in its place — styled like the 404/500 pages — preserving the
status code. The template receives { status, status_text, message, request_path, dev_mode }. Repeatable for multiple codes.
404 and 500 have dedicated methods (Self::not_found_template /
Self::server_error_template); use this for everything else.
App::builder()
.error_template(StatusCode::TOO_MANY_REQUESTS, "errors/429.html")
.error_template(StatusCode::FORBIDDEN, "errors/403.html")Sourcepub fn on_server_error<F>(self, hook: F) -> Self
pub fn on_server_error<F>(self, hook: F) -> Self
Register a hook that fires on every internal server error (500).
The closure receives:
error_display: &str— theDisplayform of the error or the stringified panic payload.request_path: &str— the URI path of the failing request (empty for panic-path errors where path isn’t yet available).
The hook runs synchronously before the 500 template is rendered. It cannot change the response — use it to log to an external service (Sentry, Datadog, a file, etc.).
App::builder()
.on_server_error(|err, path| {
tracing::error!(err, path, "500 error");
})
.build()?Sourcepub fn disable_default_error_pages(self) -> Self
pub fn disable_default_error_pages(self) -> Self
Disable the built-in default 404/500 templates.
By default, when the user hasn’t called .not_found_template(...) or
.server_error_template(...), umbral renders its own embedded Tailwind
error pages. Call this method to revert to axum’s built-in behaviour:
a plain-text “Not Found” on 404 and an empty 500 body on panic.
App::builder()
.disable_default_error_pages()
.build()?Sourcepub fn cors(self, config: CorsConfig) -> Self
pub fn cors(self, config: CorsConfig) -> Self
Install a CORS policy as the outermost middleware.
The framework doesn’t install a CorsLayer by default —
same-origin requests need no policy, and CORS is too
security-sensitive to enable implicitly. Pass a
crate::cors::CorsConfig (start from
CorsConfig::strict for
production or CorsConfig::permissive
for dev).
use umbral::prelude::*;
use umbral::cors::CorsConfig;
App::builder()
.cors(CorsConfig::strict()
.allow_origin("https://app.example.com")
.allow_credentials(true))
.build()
.await?The layer is applied LAST in the middleware chain so it
becomes the outermost wrapper — preflight OPTIONS is
answered before any plugin / handler sees the request, and
the response headers are added on the way back out
regardless of which downstream layer produced the body.
Sourcepub fn cors_for(self, prefix: impl Into<String>, config: CorsConfig) -> Self
pub fn cors_for(self, prefix: impl Into<String>, config: CorsConfig) -> Self
Apply a CORS policy scoped to requests whose path starts with prefix
(e.g. "/api"), leaving every other route’s responses untouched. The
path-scoped counterpart to cors — the shape you want for
“CORS on the REST API, not the HTML pages.” Call repeatedly for several
prefixes. Scoped policies are applied after (outside) the global one.
use umbral::cors::CorsConfig;
App::builder()
.cors_for("/api", CorsConfig::strict()
.allow_origins(vec!["https://app.example.com"])
.allow_credentials(true))
.build()
.await?Sourcepub fn atomic_transactions(self, enabled: bool) -> Self
pub fn atomic_transactions(self, enabled: bool) -> Self
Default every ORM write to run inside its own transaction.
When enabled = true, terminals that opt into the contract
(Manager::create, Manager::bulk_create,
Manager::get_or_create, QuerySet::update_values,
QuerySet::delete) wrap their work in a BEGIN / COMMIT pair
unless the caller explicitly opts out with .non_atomic().
This is the safe-by-default posture: a framework that claims “secure by default” should also be “transaction-safe by default.” Opting out matters mostly for high-throughput seed scripts that already wrap an outer transaction themselves.
Without this flag the framework’s behaviour is unchanged —
writes run with whatever transaction the caller arranges. The
per-call .atomic() / .non_atomic() overrides still work.
Sourcepub fn compression(self) -> Self
pub fn compression(self) -> Self
Compress responses with gzip / brotli (a tower-http
CompressionLayer). The algorithm is chosen from the request’s
Accept-Encoding; already-encoded or non-compressible content types
are skipped automatically.
Off by default: in most deployments the reverse proxy (nginx, a CDN) already compresses, and doing it twice is wasted CPU. Enable this when you serve directly (a single binary with no proxy in front).
Sourcepub fn middleware<M: Middleware>(self, mw: M) -> Self
pub fn middleware<M: Middleware>(self, mw: M) -> Self
Register a framework-level Middleware
(feature #68) with before_request / after_response hooks.
App-level middleware is added to the stack before any plugin’s
contribution, so its before_request runs first and its
after_response runs last (it’s the outermost layer of the onion).
Call this multiple times to register several, in order.
Use this for the common “look at every request / response” case.
For a real tower Layer (timeouts, body limits) reach for the
router directly via a plugin’s wrap_router.
Sourcepub fn build(self) -> Result<App, BuildError>
pub fn build(self) -> Result<App, BuildError>
Finalize the application.
Phases (see spec 01 §Mechanics and invariants and spec 02 §Dependency ordering):
- Collect. Gather settings, databases, and router from
builder-local state. Settings must be set explicitly via
.settings(...); the “default” database pool must be registered via.database("default", pool). The caller opens the pool first (withumbral::db::connect(...).await) and hands it to the builder. This matches the canonical pattern in spec 01-app-and-settings.md. - Validate plugins. Reject the reserved
"app"name, reject duplicatePlugin::name()s, verify every entry in adependencies()list points at a registered plugin, and compute a stable topological order. Cycles surface asBuildError::PluginCycle. - Detect backend.
backend::detect(&settings.database_url)picks one of the shippedDatabaseBackendimpls (M4 abstraction). An unknown URL scheme (mysql / oracle / etc.) fails here, before any system check runs. - Publish ambient state. Write settings, pools, and the
active backend into their
OnceLocks. The model registry carries one entry per plugin (the implicit"app"plus every registered plugin’sPlugin::models()). - System check. Run framework-built-in checks plus every
plugin’s
system_checks()(concatenated in topological order) against the just-published context. Errors block boot; warnings log and continue. - Build router. Start from the hand-written router (or a
fallback handler), then merge every plugin’s
routes()in topological order. axum’sRouter::mergepanics on duplicate routes with a clear message. - Fire
on_ready. Call each plugin’son_ready(&AppContext)in topological order. A failure here surfaces asBuildError::PluginOnReady.
build() is intentionally sync. Earlier iterations auto-opened
the default pool from settings.database_url by spinning up a
throwaway tokio runtime to drive db::connect. That panicked
when called from inside any caller that was already in a tokio
runtime (“Cannot start a runtime from within a runtime”), which
is every realistic case. Requiring an explicit .database(...)
is both spec-correct and avoids the trap.
Trait Implementations§
Auto Trait Implementations§
impl !RefUnwindSafe for AppBuilder
impl !UnwindSafe for AppBuilder
impl Freeze for AppBuilder
impl Send for AppBuilder
impl Sync for AppBuilder
impl Unpin for AppBuilder
impl UnsafeUnpin for AppBuilder
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
impl<A, B, T> HttpServerConnExec<A, B> for Twhere
B: Body,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> Paint for Twhere
T: ?Sized,
impl<T> Paint for Twhere
T: ?Sized,
Source§fn fg(&self, value: Color) -> Painted<&T>
fn fg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self with the foreground set to
value.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like red() and
green(), which have the same functionality but are
pithier.
§Example
Set foreground color to white using fg():
use yansi::{Paint, Color};
painted.fg(Color::White);Set foreground color to white using white().
use yansi::Paint;
painted.white();Source§fn bright_black(&self) -> Painted<&T>
fn bright_black(&self) -> Painted<&T>
Source§fn bright_red(&self) -> Painted<&T>
fn bright_red(&self) -> Painted<&T>
Source§fn bright_green(&self) -> Painted<&T>
fn bright_green(&self) -> Painted<&T>
Source§fn bright_yellow(&self) -> Painted<&T>
fn bright_yellow(&self) -> Painted<&T>
Source§fn bright_blue(&self) -> Painted<&T>
fn bright_blue(&self) -> Painted<&T>
Source§fn bright_magenta(&self) -> Painted<&T>
fn bright_magenta(&self) -> Painted<&T>
Source§fn bright_cyan(&self) -> Painted<&T>
fn bright_cyan(&self) -> Painted<&T>
Source§fn bright_white(&self) -> Painted<&T>
fn bright_white(&self) -> Painted<&T>
Source§fn bg(&self, value: Color) -> Painted<&T>
fn bg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self with the background set to
value.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like on_red() and
on_green(), which have the same functionality but
are pithier.
§Example
Set background color to red using fg():
use yansi::{Paint, Color};
painted.bg(Color::Red);Set background color to red using on_red().
use yansi::Paint;
painted.on_red();Source§fn on_primary(&self) -> Painted<&T>
fn on_primary(&self) -> Painted<&T>
Source§fn on_magenta(&self) -> Painted<&T>
fn on_magenta(&self) -> Painted<&T>
Source§fn on_bright_black(&self) -> Painted<&T>
fn on_bright_black(&self) -> Painted<&T>
Source§fn on_bright_red(&self) -> Painted<&T>
fn on_bright_red(&self) -> Painted<&T>
Source§fn on_bright_green(&self) -> Painted<&T>
fn on_bright_green(&self) -> Painted<&T>
Source§fn on_bright_yellow(&self) -> Painted<&T>
fn on_bright_yellow(&self) -> Painted<&T>
Source§fn on_bright_blue(&self) -> Painted<&T>
fn on_bright_blue(&self) -> Painted<&T>
Source§fn on_bright_magenta(&self) -> Painted<&T>
fn on_bright_magenta(&self) -> Painted<&T>
Source§fn on_bright_cyan(&self) -> Painted<&T>
fn on_bright_cyan(&self) -> Painted<&T>
Source§fn on_bright_white(&self) -> Painted<&T>
fn on_bright_white(&self) -> Painted<&T>
Source§fn attr(&self, value: Attribute) -> Painted<&T>
fn attr(&self, value: Attribute) -> Painted<&T>
Enables the styling Attribute value.
This method should be used rarely. Instead, prefer to use
attribute-specific builder methods like bold() and
underline(), which have the same functionality
but are pithier.
§Example
Make text bold using attr():
use yansi::{Paint, Attribute};
painted.attr(Attribute::Bold);Make text bold using using bold().
use yansi::Paint;
painted.bold();Source§fn rapid_blink(&self) -> Painted<&T>
fn rapid_blink(&self) -> Painted<&T>
Source§fn quirk(&self, value: Quirk) -> Painted<&T>
fn quirk(&self, value: Quirk) -> Painted<&T>
Enables the yansi Quirk value.
This method should be used rarely. Instead, prefer to use quirk-specific
builder methods like mask() and
wrap(), which have the same functionality but are
pithier.
§Example
Enable wrapping using .quirk():
use yansi::{Paint, Quirk};
painted.quirk(Quirk::Wrap);Enable wrapping using wrap().
use yansi::Paint;
painted.wrap();Source§fn clear(&self) -> Painted<&T>
👎Deprecated since 1.0.1: renamed to resetting() due to conflicts with Vec::clear().
The clear() method will be removed in a future release.
fn clear(&self) -> Painted<&T>
renamed to resetting() due to conflicts with Vec::clear().
The clear() method will be removed in a future release.
Source§fn whenever(&self, value: Condition) -> Painted<&T>
fn whenever(&self, value: Condition) -> Painted<&T>
Conditionally enable styling based on whether the Condition value
applies. Replaces any previous condition.
See the crate level docs for more details.
§Example
Enable styling painted only when both stdout and stderr are TTYs:
use yansi::{Paint, Condition};
painted.red().on_yellow().whenever(Condition::STDOUTERR_ARE_TTY);