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:
- route handlers, registered via
Blueprint::route
- constructors, registered via
Blueprint::constructor
- wrapping middlewares, registered via
Blueprint::wrap
- fallback handlers, registered via
Blueprint::fallback
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
impl Blueprint
sourcepub fn route(
&mut self,
method_guard: MethodGuard,
path: &str,
callable: RawIdentifiers,
) -> RegisteredRoute<'_>
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));
sourcepub fn prebuilt(&mut self, type_: RawIdentifiers) -> RegisteredPrebuiltType<'_>
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.
sourcepub fn constructor(
&mut self,
callable: RawIdentifiers,
lifecycle: Lifecycle,
) -> RegisteredConstructor<'_>
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);
sourcepub fn singleton(
&mut self,
callable: RawIdentifiers,
) -> RegisteredConstructor<'_>
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));
sourcepub fn request_scoped(
&mut self,
callable: RawIdentifiers,
) -> RegisteredConstructor<'_>
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));
sourcepub fn transient(
&mut self,
callable: RawIdentifiers,
) -> RegisteredConstructor<'_>
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));
sourcepub fn wrap(
&mut self,
callable: RawIdentifiers,
) -> RegisteredWrappingMiddleware<'_>
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
}
sourcepub fn post_process(
&mut self,
callable: RawIdentifiers,
) -> RegisteredPostProcessingMiddleware<'_>
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
}
sourcepub fn pre_process(
&mut self,
callable: RawIdentifiers,
) -> RegisteredPreProcessingMiddleware<'_>
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
}
sourcepub fn nest(&mut self, blueprint: Blueprint)
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.
sourcepub fn prefix(&mut self, prefix: &str) -> NestingConditions<'_>
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
}
sourcepub fn domain(&mut self, domain: &str) -> NestingConditions<'_>
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.
sourcepub fn fallback(&mut self, callable: RawIdentifiers) -> RegisteredFallback<'_>
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
).
sourcepub fn error_observer(
&mut self,
callable: RawIdentifiers,
) -> RegisteredErrorObserver<'_>
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));
sourcepub fn push_component(&mut self, component: impl Into<Component>) -> usize
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).