Skip to main content

App

Struct App 

Source
pub struct App<S = ()> { /* private fields */ }
Expand description

A Kerniq application: routes, state, middleware, configuration and lifecycle in one builder.

use kerniq_server::prelude::*;

#[tokio::main]
async fn main() -> Result<()> {
    App::new()
        .get("/", || async { "Hello from Kerniq" })
        .run("127.0.0.1:3000")
        .await
}

§State

App<S> is parameterized by its state type. App::new creates an App<()>; App::state switches to App<T>, after which handlers can extract State<T>. Routes registered before state (which cannot use State<T>) are kept.

§Finalization

Nothing is assembled until the app is run or converted with App::into_axum_router. At that point:

  1. routes are combined into a single axum::Router (no runtime nesting),
  2. the JSON 404/405 fallbacks are installed,
  3. the state is provided,
  4. middleware is applied in declaration order (first = outermost), followed by the middleware implied by ServerConfig (request_timeout → max_concurrent_requests → max_body_size, outermost to innermost).

Implementations§

Source§

impl App<()>

Source

pub fn new() -> Self

Creates an application with no routes, no state and default configuration.

Source

pub fn state<T>(self, state: T) -> App<T>
where T: Clone + Send + Sync + 'static,

Sets the application state, available to handlers via State<T>.

The state is cloned for each request that extracts it, so it should be cheap to clone: wrap expensive resources in Arc (connection pools usually already are). Sub-states can be extracted with Axum’s FromRef.

use std::sync::Arc;
use kerniq_server::prelude::*;

#[derive(Clone)]
struct AppState {
    service_name: Arc<str>,
}

async fn name(State(state): State<AppState>) -> String {
    state.service_name.to_string()
}

let app = App::new()
    .state(AppState { service_name: "customer-api".into() })
    .get("/name", name);
Source§

impl<S> App<S>
where S: Clone + Send + Sync + 'static,

Source

pub fn get<H, T>(self, path: &str, handler: H) -> Self
where H: Handler<T, S>, T: 'static,

Registers a GET handler (also answers HEAD).

Paths use {name} captures; :name is accepted and converted.

Source

pub fn post<H, T>(self, path: &str, handler: H) -> Self
where H: Handler<T, S>, T: 'static,

Registers a POST handler.

Source

pub fn put<H, T>(self, path: &str, handler: H) -> Self
where H: Handler<T, S>, T: 'static,

Registers a PUT handler.

Source

pub fn patch<H, T>(self, path: &str, handler: H) -> Self
where H: Handler<T, S>, T: 'static,

Registers a PATCH handler.

Source

pub fn delete<H, T>(self, path: &str, handler: H) -> Self
where H: Handler<T, S>, T: 'static,

Registers a DELETE handler.

Source

pub fn options<H, T>(self, path: &str, handler: H) -> Self
where H: Handler<T, S>, T: 'static,

Registers an OPTIONS handler.

Source

pub fn head<H, T>(self, path: &str, handler: H) -> Self
where H: Handler<T, S>, T: 'static,

Registers a HEAD handler.

Source

pub fn trace<H, T>(self, path: &str, handler: H) -> Self
where H: Handler<T, S>, T: 'static,

Registers a TRACE handler.

Source

pub fn any<H, T>(self, path: &str, handler: H) -> Self
where H: Handler<T, S>, T: 'static,

Registers a handler for every method.

Source

pub fn on<H, T>(self, method: Method, path: &str, handler: H) -> Self
where H: Handler<T, S>, T: 'static,

Registers a handler for an arbitrary standard method. See Router::on.

Source

pub fn route(self, path: &str, method_router: MethodRouter<S>) -> Self

Registers an Axum MethodRouter (advanced use: per-route layers, several methods at once). See Router::route.

Source

pub fn group(self, f: impl FnOnce(&mut Group<'_, S>)) -> Self

Registers several routes in a block. See Router::group.

Source

pub fn mount(self, path: &str, router: Router<S>) -> Self

Mounts a Kerniq Router under path (combined with the router’s own prefix).

use kerniq_server::prelude::*;

let users = Router::new().get("/", list).post("/", create);
let app = App::new()
    .mount("/users", users)
    .mount("/accounts", Router::new().get("/", accounts));
Source

pub fn merge(self, router: Router<S>) -> Self

Merges a Kerniq Router at its own prefix.

use kerniq_server::prelude::*;

let api = Router::new()
    .prefix("/api/v1")
    .group(|r| { r.get("/users", users); });
let app = App::new().merge(api);
Source

pub fn mount_axum(self, path: &str, router: Router<S>) -> Self

Mounts an existing axum::Router under path ("/" merges it at the root). Its routes, layers and handlers are used as-is.

Source

pub fn middleware<M: Middleware>(self, middleware: M) -> Self

Adds application-wide middleware. The first middleware added is the outermost. Applies to every route and to the 404 fallback, regardless of whether routes are registered before or after this call.

Source

pub fn layer<L>(self, layer: L) -> Self

Adds an application-wide Tower layer. Shares the ordered stack with middleware.

Source

pub fn fallback<H, T>(self, handler: H) -> Self
where H: Handler<T, S>, T: 'static,

Replaces the default JSON 404 NOT_FOUND fallback.

Source

pub fn observability(self, observability: Observability) -> Self

Available on crate feature observability only.

Enables observability (feature observability).

use kerniq_server::prelude::*;
use kerniq_server::observability::prelude::*;

#[tokio::main]
async fn main() -> Result<()> {
    let observability = Observability::builder()
        .service_name("payment-api")
        .metrics(MetricsConfig::new())
        .build()
        .await?;

    App::new()
        .observability(observability)
        .get("/health", || async { "OK" })
        .run("0.0.0.0:3000")
        .await
}

At finalization:

  • HTTP instrumentation (metrics, a span per request, W3C context extraction) becomes the outermost middleware, so it measures everything inside it, including timeouts, rate limiting and the 404 fallback;
  • a Prometheus endpoint configured on the application listener is added outside all middleware, so scrapes are never instrumented, rate-limited or timed out; one configured with its own listener is served by a background task bound when the app starts.

Under run/serve, telemetry is flushed and shut down after connections and background tasks have drained, bounded by the observability shutdown timeout. With into_axum_router the application owns the handle and calls shutdown itself.

Source

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

Sets the server configuration.

Source

pub fn server_config(&self) -> &ServerConfig

The current server configuration.

Source

pub fn background_task<F, Fut>(self, name: &'static str, task: F) -> Self
where F: FnOnce(Shutdown) -> Fut + Send + 'static, Fut: Future<Output = ()> + Send + 'static,

Registers a background task started when the server starts. It receives a Shutdown handle and should return promptly once it triggers; the server waits up to shutdown_timeout for it.

use std::time::Duration;
use kerniq_server::prelude::*;

let app = App::new().background_task("heartbeat", |shutdown: Shutdown| async move {
    let mut tick = tokio::time::interval(Duration::from_secs(30));
    loop {
        tokio::select! {
            _ = tick.tick() => tracing::info!("still alive"),
            () = shutdown.wait() => break,
        }
    }
});
Source

pub fn shutdown_signal<F>(self, signal: F) -> Self
where F: Future<Output = ()> + Send + 'static,

Replaces the default shutdown trigger (Ctrl+C / SIGTERM) with signal. Shutdown can also be triggered at any time through shutdown_handle.

Source

pub fn shutdown_handle(&self) -> Shutdown

A handle that triggers or observes this app’s shutdown. Store it in state so long-lived handlers (WebSockets, streams) can finish when the server stops.

Source

pub fn routes(&self) -> &[RouteInfo]

The registered routes (for diagnostics; mounted Axum routers appear as a single opaque entry).

Source

pub fn into_axum_router(self) -> Router

Finalizes the application into a plain axum::Router, with fallbacks, state and all middleware applied.

Use this to run Kerniq under axum::serve, embed it into a larger Axum application, or test it with tower::ServiceExt::oneshot. Lifecycle features (background tasks, shutdown signal, connection limits) belong to App::run and are not part of the router.

Source

pub async fn run(self, address: impl AsRef<str>) -> Result<()>

Binds address and serves until shutdown. Requires a Tokio runtime (for example #[tokio::main]).

address is anything tokio::net::TcpListener::bind accepts as a string: "127.0.0.1:3000", "0.0.0.0:8080", "localhost:3000", "[::1]:3000". To use the configured address, pass config.address().

Source

pub async fn serve(self, listener: TcpListener) -> Result<()>

Serves on an already-bound listener (useful for tests with port 0, socket activation, or binding before dropping privileges).

Source

pub fn run_blocking(self, address: impl AsRef<str>) -> Result<()>

Like run, but creates and owns a multi-threaded Tokio runtime (with workers threads, if configured).

use kerniq_server::prelude::*;

fn main() -> Result<()> {
    App::new()
        .get("/", || async { "OK" })
        .run_blocking("0.0.0.0:3000")
}
§Errors

Returns an error (and does not start) if called from inside a Tokio runtime: nested runtimes are not allowed. Use .run(addr).await there.

Trait Implementations§

Source§

impl<S> Debug for App<S>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for App<()>

Source§

fn default() -> Self

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

impl<S> From<App<S>> for Router
where S: Clone + Send + Sync + 'static,

Source§

fn from(app: App<S>) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

§

impl<S = ()> !RefUnwindSafe for App<S>

§

impl<S = ()> !Sync for App<S>

§

impl<S = ()> !UnwindSafe for App<S>

§

impl<S> Forget for App<S>

§

impl<S> Freeze for App<S>
where Router<S>: Freeze, S: Freeze,

§

impl<S> Send for App<S>
where Router<S>: Send, S: Send,

§

impl<S> Unpin for App<S>
where Router<S>: Unpin, S: Unpin,

§

impl<S> UnsafeUnpin for App<S>

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> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FutureExt for T

Source§

fn with_context(self, otel_cx: Context) -> WithContext<Self> ⓘ

Attaches the provided Context to this type, returning a WithContext wrapper. Read more
Source§

fn with_current_context(self) -> WithContext<Self> ⓘ

Attaches the current Context to this type, returning a WithContext wrapper. Read more
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> IntoRequest<T> for T

Source§

fn into_request(self) -> Request<T>

Wrap the input message T in a tonic::Request
Source§

impl<L> LayerExt<L> for L

Source§

fn named_layer<S>(&self, service: S) -> Layered<<L as Layer<S>>::Service, S>
where L: Layer<S>,

Applies the layer to a service and wraps it in Layered.
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> ServiceExt for T

Source§

fn compression(self) -> Compression<Self>
where Self: Sized,

Available on crate features compression-br or compression-deflate or compression-gzip or compression-zstd only.
Compresses response bodies. Read more
Source§

fn trace_for_http(self) -> Trace<Self, SharedClassifier<ServerErrorsAsFailures>>
where Self: Sized,

Available on crate feature trace only.
High level tracing that classifies responses using HTTP status codes. Read more
Source§

fn trace_for_grpc(self) -> Trace<Self, SharedClassifier<GrpcErrorsAsFailures>>
where Self: Sized,

Available on crate feature trace only.
High level tracing that classifies responses using gRPC headers. Read more
Source§

fn set_request_id<M>( self, header_name: HeaderName, make_request_id: M, ) -> SetRequestId<Self, M>
where Self: Sized, M: MakeRequestId,

Available on crate feature request-id only.
Add request id header and extension. Read more
Source§

fn set_x_request_id<M>(self, make_request_id: M) -> SetRequestId<Self, M>
where Self: Sized, M: MakeRequestId,

Available on crate feature request-id only.
Add request id header and extension, using x-request-id as the header name. Read more
Source§

fn propagate_request_id( self, header_name: HeaderName, ) -> PropagateRequestId<Self>
where Self: Sized,

Available on crate feature request-id only.
Propgate request ids from requests to responses. Read more
Source§

fn propagate_x_request_id(self) -> PropagateRequestId<Self>
where Self: Sized,

Available on crate feature request-id only.
Propgate request ids from requests to responses, using x-request-id as the header name. Read more
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