Skip to main content

App

Struct App 

Source
pub struct App { /* private fields */ }
Expand description

The application builder.

fn main() -> renox::Result {
    App::new()
        .migrations(renox::migrations!())
        .module(Products)
        .job::<SendReceipt>()
        .schedule(|s| { s.daily_at("02:00", "cleanup", cleanup); })
        .seeder(seed)
        .run()
}

The built binary is also the app’s command line, like Laravel’s artisan: my-app migrate, my-app queue:work, my-app help.

Implementations§

Source§

impl App

Source

pub fn new() -> Self

Creates an application that loads its configuration from .env on start.

Source

pub fn detect_locale(self) -> Self

Picks a visitor’s language from their browser (Accept-Language) when they haven’t chosen one: the first of their languages this app has texts for (the built-in en and id, or a resources/lang/<locale>.json), else APP_LOCALE. A language set with i18n::remember_locale still wins. Responses then carry Vary: Accept-Language, so caches keep the languages apart.

App::new().detect_locale()
Source

pub fn layer<L>(self, layer: L) -> Self
where L: Layer<Route> + Clone + Send + Sync + 'static, L::Service: Service<Request> + Clone + Send + Sync + 'static, <L::Service as Service<Request>>::Response: IntoResponse + 'static, <L::Service as Service<Request>>::Error: Into<Infallible> + 'static, <L::Service as Service<Request>>::Future: Send + 'static,

Wraps every route of the app’s modules in a tower layer, e.g. a middleware function (framework routes such as /health and public/ files aren’t wrapped). It runs after Renox has loaded the session and the user, so it can use AuthUser or Session:

use renox::axum::extract::Request;
use renox::axum::middleware::{Next, from_fn};

async fn stamp(user: Option<AuthUser>, req: Request, next: Next) -> Response {
    let mut res = next.run(req).await;
    let who = if user.is_some() { "member" } else { "guest" };
    res.headers_mut().insert("x-visitor", who.parse().unwrap());
    res
}

App::new().layer(from_fn(stamp))

Layers run in the order added: the first one sees the request first.

Source

pub fn csp(self, allow: impl FnOnce(&mut Csp)) -> Self

Allows other sites in the Content-Security-Policy, e.g. .csp(|csp| { csp.allow("script-src", "https://www.googletagmanager.com"); }).

Source

pub fn embed(self, embedded: Embedded) -> Self

Views, translations and public files compiled into the binary: .embed(renox::embedded!()). They’re used when APP_DEBUG is off; while debugging, files are read from disk so edits show up at once.

Source

pub fn with_config(config: Config) -> Self

Uses the given configuration instead of loading it from the environment.

Source

pub fn config(self, config: Config) -> Self

Replaces the configuration, e.g. in tests.

Source

pub fn module(self, module: impl Module) -> Self

Adds a module: its routes, migrations and registrations.

Source

pub fn migrations(self, migrations: &[Migration]) -> Self

Registers app-level migrations, usually renox::migrations!().

Source

pub fn seeder<F, Fut>(self, seeder: F) -> Self
where F: Fn(AppState) -> Fut + Send + Sync + 'static, Fut: Future<Output = Result> + Send + 'static,

Registers a seeder for db:seed. Seeders run in registration order, in the app’s context (model hooks and renox::context::app() see it), and get the app’s state: state.db, its config, storage…

App::new().seeder(|state| async move {
    Product::factory().count(50).create(&state.db).await?;
    Ok(())
})
Source

pub fn gate( self, name: &str, check: impl Fn(&User) -> bool + Send + Sync + 'static, ) -> Self

Defines a gate: an ability that depends only on the user.

App::new().gate("admin", |user| user.email.ends_with("@shop.example"))
// in a handler: auth.gate("admin")?;   in a template: {% if can('admin') %}
Source

pub fn gate_before( self, check: impl Fn(&User, &str) -> Option<bool> + Send + Sync + 'static, ) -> Self

A gate that may query the database, e.g. whether the user belongs to a team. Check it in handlers with auth.gate_async(name).await?; templates can’t wait for it (can() denies it), so pass its answer in the view’s context.

App::new().gate_async("billing", |user, state| async move {
    let n: i64 = renox::db::sql("SELECT COUNT(*) FROM team_admins WHERE user_id = ?")
        .bind(user.id)
        .scalar(&state.db)
        .await?;
    Ok(n > 0)
})
// in a handler: auth.gate_async("billing").await?;

Asked before every gate, permission and policy check: Some(true) allows, Some(false) denies, None goes on to the check itself. Typically lets super-admins do everything.

App::new().gate_before(|user, _ability| {
    (user.get::<String>("role").as_deref() == Some("owner")).then_some(true)
})
Source

pub fn gate_async<F, Fut>(self, name: &str, check: F) -> Self
where F: Fn(User, AppState) -> Fut + Send + Sync + 'static, Fut: Future<Output = Result<bool>> + Send + 'static,

Defines the gate name with a check that can await, e.g. to query the database. can(name) and require_gate ask it like any gate.

Source

pub fn webhook<W: Webhook>(self) -> Self

Receives W’s webhooks: see renox::webhook. Also add the route with Routes::webhook::<W>(path).

Source

pub fn job<J: Job>(self) -> Self

Lets queue workers run jobs of type J.

Source

pub fn listen<E, F, Fut>(self, listener: F) -> Self
where E: Event, F: Fn(E, AppState) -> Fut + Send + Sync + 'static, Fut: Future<Output = Result> + Send + 'static,

Runs listener whenever an E is emitted with state.emit(..).

Source

pub fn command<F, Fut>(self, name: &str, about: &str, run: F) -> Self
where F: Fn(Args, AppState) -> Fut + Send + Sync + 'static, Fut: Future<Output = Result> + Send + 'static,

Adds a command the app binary runs: my-app <name> [args], e.g. to create the first admin or run an import. See crate::command.

Source

pub fn typed_command<T: AppCommand>(self) -> Self

A command whose arguments are declared with clap; see AppCommand.

Source

pub fn rate_limiter( self, name: &str, rule: impl Fn(&LimitRequest<'_>) -> Limit + Send + Sync + 'static, ) -> Self

A named rate limit for Routes::throttle_by(name), whose rule picks the limit for each request (by user, role, IP, API key…); see crate::rate_limit::Limit.

Source

pub fn report<F, Fut>(self, reporter: F) -> Self
where F: Fn(ErrorReport, AppState) -> Fut + Send + Sync + 'static, Fut: Future<Output = ()> + Send + 'static,

Sends every error that needs a person (a 500, a job that failed for good, a failed scheduled task) to reporter, e.g. an error tracker; see crate::report.

Source

pub fn channel<F, Fut>(self, name: &str, send: F) -> Self
where F: Fn(Recipient, Value, AppState) -> Fut + Send + Sync + 'static, Fut: Future<Output = Result> + Send + 'static,

Adds a notification channel (WhatsApp, SMS, Slack…); see Registry::channel.

Also sends the CSRF token as an XSRF-TOKEN cookie that scripts can read, and accepts it back in an X-XSRF-TOKEN header (Laravel’s behaviour), for a JavaScript client on the same site: axios sends it by itself. Forms and htmx don’t need it; they send _token or X-CSRF-Token.

Source

pub fn mailer( self, name: &str, settings: impl Fn(&Config) -> Result<MailConfig> + Send + Sync + 'static, ) -> Self

Adds a mailer named name, e.g. a provider for newsletters or a second SMTP account, used with state.mailer_named(name) or, by listing it in MAIL_FAILOVER, when the default mailer fails. settings runs once the configuration is loaded; MailConfig::from_env reads <PREFIX>_MAILER, _HOST, …

use renox::mail::MailConfig;

// BACKUP_HOST=smtp.other-provider.com, BACKUP_USERNAME=…; MAIL_FAILOVER=backup
App::new().mailer("backup", |config| MailConfig::from_env(config, "BACKUP"))
Source

pub fn disk( self, name: &str, settings: impl Fn(&Config) -> Result<StorageConfig> + Send + Sync + 'static, ) -> Self

Adds a storage disk named name (letters, digits, -, _), e.g. backups on another bucket, read with state.disk_named(name). settings runs once the configuration is loaded; StorageConfig::from_env reads <PREFIX>_DISK, <PREFIX>_BUCKET, … A local disk keeps its files in STORAGE_PATH/<name> unless its settings say otherwise.

use renox::storage::StorageConfig;

App::new()
    .disk("backups", |config| StorageConfig::from_env(config, "BACKUPS"))
    .disk("exports", |_| Ok(StorageConfig::default())) // local: storage/exports
Source

pub fn provide<T: Send + Sync + 'static>(self, value: T) -> Self

Makes value available everywhere the app runs: Provided<T> in handlers, state.provided::<T>() in jobs, listeners, commands and scheduled tasks. One value per type; a second one replaces the first, and it wins over a module’s (Registry::provide). See crate::Provided.

Source

pub fn share<F, Fut, T>(self, key: &str, compute: F) -> Self
where F: Fn(ViewContext) -> Fut + Send + Sync + 'static, Fut: Future<Output = Result<T>> + Send + 'static, T: Serialize,

Gives every view a value computed per request; see Registry::share.

Source

pub fn templates( self, hook: impl Fn(&mut Environment<'static>) + Send + Sync + 'static, ) -> Self

Adds template functions, filters or globals; see Registry::templates.

Source

pub fn schedule(self, define: impl FnOnce(&mut Schedule)) -> Self

Defines scheduled tasks.

Source

pub async fn boot(self) -> Result<Kernel>

Connects to the database and builds the router, without serving.

Source

pub async fn into_router(self) -> Result<Router>

Builds the router without starting a server, e.g. for tests.

Source

pub fn run(self) -> Result

Runs the command given on the command line (serve by default) on a new Tokio runtime.

Source

pub async fn run_args( self, args: impl IntoIterator<Item = impl Into<String>>, ) -> Result

Runs one command as the app binary would, e.g. from a test or a program that drives the app: ["migrate:status"], ["down", "--secret", "abc"], or [] for serve. Output goes to stdout.

App::new().run_args(["migrate"]).await?;
Source

pub async fn serve(self) -> Result

Starts the server on the current Tokio runtime.

Trait Implementations§

Source§

impl Default for App

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl !RefUnwindSafe for App

§

impl !UnwindSafe for App

§

impl Freeze for App

§

impl Send for App

§

impl Sync for App

§

impl Unpin for App

§

impl UnsafeUnpin for App

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> Fake for T

Source§

fn fake<U>(&self) -> U
where Self: FakeBase<U>,

Source§

fn fake_with_rng<U, R>(&self, rng: &mut R) -> U
where R: RngExt + ?Sized, Self: FakeBase<U>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<A, B, T> HttpServerConnExec<A, B> for T
where B: Body,

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts 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 more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more