pavex::blueprint

Struct Blueprint

source
pub struct Blueprint { /* private fields */ }
Expand description

The starting point for building an application with Pavex.

§Guide

Check out the “Project structure” section of Pavex’s guide for more details on the role of Blueprint in Pavex applications.

§Overview

A blueprint defines the runtime behaviour of your application. It keeps track of:

You can also choose to decompose your overall application into smaller sub-components, taking advantage of Blueprint::nest, Blueprint::prefix and Blueprint::domain.

The information encoded in a blueprint can be serialized via Blueprint::persist and passed as input to Pavex’s CLI to generate the application’s server SDK.

Implementations§

source§

impl Blueprint

source

pub fn new() -> Self

Create a new Blueprint.

source

pub fn route( &mut self, method_guard: MethodGuard, path: &str, callable: RawIdentifiers, ) -> RegisteredRoute<'_>

Register a request handler to be invoked when an incoming request matches the specified route.

If a request handler has already been registered for the same route, it will be overwritten.

§Guide

Check out the “Routing” section of Pavex’s guide for a thorough introduction to routing in Pavex applications.

§Example
use pavex::{f, blueprint::{Blueprint, router::GET}};
use pavex::{request::RequestHead, response::Response};

fn my_handler(request_head: &RequestHead) -> Response {
    // [...]
}

let mut bp = Blueprint::new();
bp.route(GET, "/path", f!(crate::my_handler));
source

pub fn prebuilt(&mut self, type_: RawIdentifiers) -> RegisteredPrebuiltType<'_>

Register a type to be used as input parameter to the (generated) build_application_state function.

§Guide

Check out the “Dependency injection” section of Pavex’s guide for a thorough introduction to dependency injection in Pavex applications.

source

pub fn constructor( &mut self, callable: RawIdentifiers, lifecycle: Lifecycle, ) -> RegisteredConstructor<'_>

Register a constructor.

If a constructor for the same type has already been registered, it will be overwritten.

§Guide

Check out the “Dependency injection” section of Pavex’s guide for a thorough introduction to dependency injection in Pavex applications.

§Example
use pavex::f;
use pavex::blueprint::{Blueprint, constructor::Lifecycle};

fn logger(log_level: LogLevel) -> Logger {
    // [...]
}

let mut bp = Blueprint::new();
bp.constructor(f!(crate::logger), Lifecycle::Transient);
source

pub fn singleton( &mut self, callable: RawIdentifiers, ) -> RegisteredConstructor<'_>

Register a constructor with a singleton lifecycle.

It’s a shorthand for Blueprint::constructor—refer to its documentation for more information on dependency injection in Pavex.

§Example
use pavex::f;
use pavex::blueprint::Blueprint;

fn logger(log_level: LogLevel) -> Logger {
    // [...]
}

let mut bp = Blueprint::new();
bp.singleton(f!(crate::logger));
// ^ is equivalent to:
// bp.constructor(f!(crate::logger), Lifecycle::Singleton));
source

pub fn request_scoped( &mut self, callable: RawIdentifiers, ) -> RegisteredConstructor<'_>

Register a constructor with a request-scoped lifecycle.

It’s a shorthand for Blueprint::constructor—refer to its documentation for more information on dependency injection in Pavex.

§Example
use pavex::f;
use pavex::blueprint::Blueprint;

fn logger(log_level: LogLevel) -> Logger {
    // [...]
}

let mut bp = Blueprint::new();
bp.request_scoped(f!(crate::logger));
// ^ is equivalent to:
// bp.constructor(f!(crate::logger), Lifecycle::RequestScoped));
source

pub fn transient( &mut self, callable: RawIdentifiers, ) -> RegisteredConstructor<'_>

Register a constructor with a transient lifecycle.

It’s a shorthand for Blueprint::constructor—refer to its documentation for more information on dependency injection in Pavex.

§Example
use pavex::f;
use pavex::blueprint::Blueprint;

fn logger(log_level: LogLevel) -> Logger {
    // [...]
}

let mut bp = Blueprint::new();
bp.transient(f!(crate::logger));
// ^ is equivalent to:
// bp.constructor(f!(crate::logger), Lifecycle::Transient));
source

pub fn wrap( &mut self, callable: RawIdentifiers, ) -> RegisteredWrappingMiddleware<'_>

Register a wrapping middleware.

§Guide

Check out the “Middleware” section of Pavex’s guide for a thorough introduction to middlewares in Pavex applications.

§Example: a timeout wrapper
use pavex::{f, blueprint::Blueprint, middleware::Next, response::Response};
use std::future::{IntoFuture, Future};
use std::time::Duration;
use tokio::time::{timeout, error::Elapsed};

pub async fn timeout_wrapper<C>(next: Next<C>) -> Result<Response, Elapsed>
where
    C: Future<Output = Response>
{
    timeout(Duration::from_secs(2), next.into_future()).await
}

pub fn api() -> Blueprint {
    let mut bp = Blueprint::new();
    // Register the wrapping middleware against the blueprint.
    bp.wrap(f!(crate::timeout_wrapper));
    // [...]
    bp
}
source

pub fn post_process( &mut self, callable: RawIdentifiers, ) -> RegisteredPostProcessingMiddleware<'_>

Register a post-processing middleware.

§Guide

Check out the “Middleware” section of Pavex’s guide for a thorough introduction to middlewares in Pavex applications.

§Example: a logging middleware
use pavex::{f, blueprint::Blueprint, response::Response};
use pavex_tracing::{
    RootSpan,
    fields::{http_response_status_code, HTTP_RESPONSE_STATUS_CODE}
};

pub fn response_logger(response: Response, root_span: &RootSpan) -> Response
{
    root_span.record(
        HTTP_RESPONSE_STATUS_CODE,
        http_response_status_code(&response),
    );
    response
}

pub fn api() -> Blueprint {
    let mut bp = Blueprint::new();
    // Register the post-processing middleware against the blueprint.
    bp.post_process(f!(crate::response_logger));
    // [...]
    bp
}
source

pub fn pre_process( &mut self, callable: RawIdentifiers, ) -> RegisteredPreProcessingMiddleware<'_>

Register a pre-processing middleware.

§Guide

Check out the “Middleware” section of Pavex’s guide for a thorough introduction to middlewares in Pavex applications.

§Example: path normalization
use pavex::{f, blueprint::Blueprint, response::Response};
use pavex::middleware::Processing;
use pavex::http::{HeaderValue, header::LOCATION};
use pavex::request::RequestHead;

/// If the request path ends with a `/`,
/// redirect to the same path without the trailing `/`.
pub fn redirect_to_normalized(request_head: &RequestHead) -> Processing
{
    let Some(normalized_path) = request_head.target.path().strip_suffix('/') else {
        // No need to redirect, we continue processing the request.
        return Processing::Continue;
    };
    let location = HeaderValue::from_str(normalized_path).unwrap();
    let redirect = Response::temporary_redirect().insert_header(LOCATION, location);
    // Short-circuit the request processing pipeline and return the redirect response
    // to the client without invoking downstream middlewares and the request handler.
    Processing::EarlyReturn(redirect)
}

pub fn api() -> Blueprint {
    let mut bp = Blueprint::new();
    // Register the pre-processing middleware against the blueprint.
    bp.pre_process(f!(crate::redirect_to_normalized));
    // [...]
    bp
}
source

pub fn nest(&mut self, blueprint: Blueprint)

Nest a Blueprint under the current Blueprint (the parent), without adding a common path prefix nor a domain restriction to its routes.

Check out NestingConditions::nest for more details on nesting.

source

pub fn prefix(&mut self, prefix: &str) -> NestingConditions<'_>

A common prefix will be preprended to the path of routes nested under this condition.

use pavex::f;
use pavex::blueprint::{Blueprint, router::GET};

fn app() -> Blueprint {
    let mut bp = Blueprint::new();
    // Adding `/api` as common prefix here
    bp.prefix("/api").nest(api_bp());
    bp
}

fn api_bp() -> Blueprint {
    let mut bp = Blueprint::new();
    // This will match `GET` requests to `/api/path`.
    bp.route(GET, "/path", f!(crate::handler));
    bp
}

You can also add a (sub)domain constraint, in addition to the common prefix:

use pavex::blueprint::{Blueprint, router::GET};
use pavex::f;

fn app() -> Blueprint {
   let mut bp = Blueprint::new();
   bp.prefix("/v1").domain("api.mybusiness.com").nest(api_bp());
   bp
}

fn api_bp() -> Blueprint {
   let mut bp = Blueprint::new();
  // This will match `GET` requests to `api.mybusiness.com/v1/path`.
  bp.route(GET, "/path", f!(crate::handler));
  bp
}

Check out Blueprint::domain for more details on domain restrictions.

§Restrictions

prefix must be non-empty and it must start with a /. If you don’t want to add a common prefix, check out Blueprint::nest or Blueprint::domain.

§Trailing slashes

prefix can’t end with a trailing /. This would result in routes with two consecutive / in their paths—e.g. /prefix//path—which is rarely desirable. If you actually need consecutive slashes in your route, you can add them explicitly to the path of the route registered in the nested blueprint:

use pavex::f;
use pavex::blueprint::{Blueprint, router::GET};

fn app() -> Blueprint {
    let mut bp = Blueprint::new();
    bp.prefix("/api").nest(api_bp());
    bp
}

fn api_bp() -> Blueprint {
    let mut bp = Blueprint::new();
    // This will match `GET` requests to `/api//path`.
    bp.route(GET, "//path", f!(crate::handler));
    bp
}
source

pub fn domain(&mut self, domain: &str) -> NestingConditions<'_>

Only requests to the specified domain will be forwarded to routes nested under this condition.

§Example
use pavex::blueprint::Blueprint;

let mut bp = Blueprint::new();

// We split UI and API routes into separate blueprints,
// and we serve them using different subdomains.
bp.domain("api.mybusiness.com")
  .nest(api_routes());
bp.domain("console.mybusiness.com")
  .nest(console_routes());

You can also prepend a common path prefix to all registered routes, in addition to the domain constraint:

use pavex::blueprint::{Blueprint, router::GET};
use pavex::f;

fn app() -> Blueprint {
   let mut bp = Blueprint::new();
   bp.prefix("/v1").domain("api.mybusiness.com").nest(api_bp());
   bp
}

fn api_bp() -> Blueprint {
   let mut bp = Blueprint::new();
  // This will match `GET` requests to `api.mybusiness.com/v1/path`.
  bp.route(GET, "/path", f!(crate::handler));
  bp
}

Check out Blueprint::prefix for more details on path prefixes.

§Domain detection

Domain detection is based on the value of Host header. If the header is not present in the request, the condition will be considered as not met.

Keep in mind that the Host header can be easily spoofed by the client, so you should not rely on its value for auth or other security-sensitive operations.

source

pub fn fallback(&mut self, callable: RawIdentifiers) -> RegisteredFallback<'_>

Register a fallback handler to be invoked when an incoming request does not match any of the routes you registered with Blueprint::route.

If you don’t register a fallback handler, the default framework fallback will be used instead.

If a fallback handler has already been registered against this Blueprint, it will be overwritten.

§Example
use pavex::{f, blueprint::{Blueprint, router::GET}};
use pavex::response::Response;

fn handler() -> Response {
    // [...]
}
fn fallback_handler() -> Response {
    // [...]
}

let mut bp = Blueprint::new();
bp.route(GET, "/path", f!(crate::handler));
// The fallback handler will be invoked for all the requests that don't match `/path`.
// E.g. `GET /home`, `POST /home`, `GET /home/123`, etc.
bp.fallback(f!(crate::fallback_handler));
§Signature

A fallback handler is a function (or a method) that returns a Response, either directly (if infallible) or wrapped in a Result (if fallible).

Fallback handlers can take advantage of dependency injection, like any other component. You list what you want to see injected as function parameters and Pavex will inject them for you in the generated code.

§Nesting

You can register a single fallback handler for each blueprint. If your application takes advantage of nesting, you can register a fallback against each nested blueprint in your application as well as one for the top-level blueprint.

Let’s explore how nesting affects the invocation of fallback handlers.

§Nesting without prefix

The fallback registered against a blueprint will be invoked for all the requests that match the path of a route that was directly registered against that blueprint, but don’t satisfy their method guards.

use pavex::{f, blueprint::{Blueprint, router::GET}};
use pavex::response::Response;

fn fallback_handler() -> Response {
    // [...]
}

let mut bp = Blueprint::new();
bp.route(GET, "/home", f!(crate::home_handler));
bp.nest({
    let mut bp = Blueprint::new();
    bp.route(GET, "/route", f!(crate::route_handler));
    bp.fallback(f!(crate::fallback_handler));
    bp
});

In the example above, crate::fallback_handler will be invoked for incoming POST /route requests: the path matches the path of a route registered against the nested blueprint (GET /route), but the method guard doesn’t (POST vs GET). If the incoming requests don’t have /route as their path instead (e.g. GET /street or GET /route/123), they will be handled by the fallback registered against the parent blueprint—the top-level one in this case. Since no fallback has been explicitly registered against the top-level blueprint, the default framework fallback will be used instead.

§Nesting with prefix

If the nested blueprint includes a nesting prefix (e.g. bp.nest_at("/api", api_bp)), its fallback will also be invoked for all the requests that start with the prefix but don’t match any of the route paths registered against the nested blueprint.

use pavex::{f, blueprint::{Blueprint, router::GET}};
use pavex::response::Response;

fn fallback_handler() -> Response {
    // [...]
}

let mut bp = Blueprint::new();
bp.route(GET, "/home", f!(crate::home_handler));
bp.prefix("/route").nest({
    let mut bp = Blueprint::new();
    bp.route(GET, "/", f!(crate::route_handler));
    bp.fallback(f!(crate::fallback_handler));
    bp
});

In the example above, crate::fallback_handler will be invoked for both POST /route and POST /route/123 requests: the path of the latter doesn’t match the path of the only route registered against the nested blueprint (GET /route), but it starts with the prefix of the nested blueprint (/route).

source

pub fn error_observer( &mut self, callable: RawIdentifiers, ) -> RegisteredErrorObserver<'_>

Register an error observer to intercept and report errors that occur during request handling.

§Guide

Check out the “Error observers” section of Pavex’s guide for a thorough introduction to error observers in Pavex applications.

§Example
use pavex::f;
use pavex::blueprint::Blueprint;

pub fn error_logger(e: &pavex::Error) {
    tracing::error!(
        error.msg = %e,
        error.details = ?e,
        "An error occurred while handling a request"
    );
}

let mut bp = Blueprint::new();
bp.error_observer(f!(crate::error_logger));
source

pub fn push_component(&mut self, component: impl Into<Component>) -> usize

Register a component and return its id (i.e. its index in the components vector).

source§

impl Blueprint

Methods to serialize and deserialize a Blueprint. These are used to pass the blueprint data to Pavex’s CLI.

source

pub fn persist(&self, filepath: &Path) -> Result<(), Error>

Serialize the Blueprint to a file in RON format.

The file is only written to disk if the content of the blueprint has changed.

source

pub fn load(filepath: &Path) -> Result<Self, Error>

Read a RON-encoded Blueprint from a file.

Trait Implementations§

source§

impl Default for Blueprint

source§

fn default() -> Self

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

Auto Trait Implementations§

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

source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

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

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

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> 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 = Infallible

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

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

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.
§

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

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

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
§

fn with_current_subscriber(self) -> WithDispatch<Self>

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