Skip to content

Commit

Permalink
Pre-processing middleware (LukeMathWalker#214)
Browse files Browse the repository at this point in the history
The missing piece in our middleware story.
  • Loading branch information
LukeMathWalker authored Mar 23, 2024
1 parent eef2220 commit dbe9683
Show file tree
Hide file tree
Showing 49 changed files with 1,923 additions and 364 deletions.
81 changes: 77 additions & 4 deletions libs/pavex/src/blueprint/blueprint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ use crate::blueprint::error_observer::RegisteredErrorObserver;
use crate::blueprint::router::RegisteredFallback;
use pavex_bp_schema::{
Blueprint as BlueprintSchema, Constructor, Fallback, NestedBlueprint, PostProcessingMiddleware,
Route, WrappingMiddleware,
PreProcessingMiddleware, Route, WrappingMiddleware,
};
use pavex_reflection::Location;

use super::constructor::{Lifecycle, RegisteredConstructor};
use super::middleware::{RegisteredPostProcessingMiddleware, RegisteredWrappingMiddleware};
use super::middleware::{
RegisteredPostProcessingMiddleware, RegisteredPreProcessingMiddleware,
RegisteredWrappingMiddleware,
};
use super::reflection::RawCallable;
use super::router::{MethodGuard, RegisteredRoute};

Expand Down Expand Up @@ -367,8 +370,8 @@ impl Blueprint {
///
/// pub fn api() -> Blueprint {
/// let mut bp = Blueprint::new();
/// // Register the wrapping middleware against the blueprint.
/// bp.wrap(f!(crate::response_logger));
/// // Register the post-processing middleware against the blueprint.
/// bp.post_process(f!(crate::response_logger));
/// // [...]
/// bp
/// }
Expand All @@ -387,6 +390,61 @@ impl Blueprint {
}
}

#[track_caller]
/// Register a pre-processing middleware.
///
/// # Guide
///
/// Check out the ["Middleware"](https://pavex.dev/docs/guide/middleware)
/// section of Pavex's guide for a thorough introduction to middlewares
/// in Pavex applications.
///
/// # Example: path normalization
///
/// ```rust
/// use http::HeaderValue;
/// use pavex::{f, blueprint::Blueprint, response::Response};
/// use pavex::middleware::Processing;
/// use pavex::http::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
/// }
/// ```
#[doc(alias = "middleware")]
#[doc(alias = "preprocess")]
pub fn pre_process(&mut self, callable: RawCallable) -> RegisteredPreProcessingMiddleware {
let registered = PreProcessingMiddleware {
middleware: raw_callable2registered_callable(callable),
error_handler: None,
};
let component_id = self.push_component(registered);
RegisteredPreProcessingMiddleware {
blueprint: &mut self.schema,
component_id,
}
}

pub(super) fn register_post_processing_middleware(
&mut self,
mw: super::middleware::PostProcessingMiddleware,
Expand All @@ -402,6 +460,21 @@ impl Blueprint {
}
}

pub(super) fn register_pre_processing_middleware(
&mut self,
mw: super::middleware::PreProcessingMiddleware,
) -> RegisteredPreProcessingMiddleware {
let mw = PostProcessingMiddleware {
middleware: mw.callable,
error_handler: mw.error_handler,
};
let component_id = self.push_component(mw);
RegisteredPreProcessingMiddleware {
component_id,
blueprint: &mut self.schema,
}
}

#[track_caller]
/// Nest a [`Blueprint`] under the current [`Blueprint`] (the parent), adding a common prefix to all the new routes.
///
Expand Down
10 changes: 6 additions & 4 deletions libs/pavex/src/blueprint/middleware/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
//!
//! Check out the ["Middleware"](https://pavex.dev/docs/guide/middleware) section of Pavex's guide
//! for a thorough introduction to middlewares in Pavex applications.
mod registered;
mod unregistered;
mod post;
mod pre;
mod wrapping;

pub use registered::{RegisteredPostProcessingMiddleware, RegisteredWrappingMiddleware};
pub use unregistered::{PostProcessingMiddleware, WrappingMiddleware};
pub use post::{PostProcessingMiddleware, RegisteredPostProcessingMiddleware};
pub use pre::{PreProcessingMiddleware, RegisteredPreProcessingMiddleware};
pub use wrapping::{RegisteredWrappingMiddleware, WrappingMiddleware};
5 changes: 5 additions & 0 deletions libs/pavex/src/blueprint/middleware/post/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod registered;
mod unregistered;

pub use registered::RegisteredPostProcessingMiddleware;
pub use unregistered::PostProcessingMiddleware;
68 changes: 68 additions & 0 deletions libs/pavex/src/blueprint/middleware/post/registered.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use crate::blueprint::conversions::raw_callable2registered_callable;
use crate::blueprint::reflection::RawCallable;
use pavex_bp_schema::{Blueprint as BlueprintSchema, Component, PostProcessingMiddleware};

/// The type returned by [`Blueprint::post_process`].
///
/// It allows you to further configure the behaviour of post-processing middleware
/// you just registered.
///
/// [`Blueprint::post_process`]: crate::blueprint::Blueprint::post_process
pub struct RegisteredPostProcessingMiddleware<'a> {
pub(crate) blueprint: &'a mut BlueprintSchema,
/// The index of the registered middleware in the blueprint's `components` vector.
pub(crate) component_id: usize,
}

impl<'a> RegisteredPostProcessingMiddleware<'a> {
#[track_caller]
/// Register an error handler.
///
/// If an error handler has already been registered for this middleware, it will be
/// overwritten.
///
/// # Guide
///
/// Check out the ["Error handlers"](https://pavex.dev/docs/guide/errors/error_handlers)
/// section of Pavex's guide for a thorough introduction to error handlers
/// in Pavex applications.
///
/// # Example
///
/// ```rust
/// use pavex::{f, blueprint::Blueprint};
/// use pavex::response::Response;
/// # struct SizeError;
///
/// // 👇 a fallible post-processing middleware
/// fn max_response_size(response: Response) -> Result<Response, SizeError>
/// {
/// // [...]
/// # todo!()
/// }
///
/// fn error_to_response(error: &SizeError) -> Response {
/// // [...]
/// # todo!()
/// }
///
/// # fn main() {
/// let mut bp = Blueprint::new();
/// bp.post_process(f!(crate::max_response_size))
/// .error_handler(f!(crate::error_to_response));
/// # }
/// ```
pub fn error_handler(mut self, error_handler: RawCallable) -> Self {
let callable = raw_callable2registered_callable(error_handler);
self.post_processing_middleware().error_handler = Some(callable);
self
}

fn post_processing_middleware(&mut self) -> &mut PostProcessingMiddleware {
let component = &mut self.blueprint.components[self.component_id];
let Component::PostProcessingMiddleware(c) = component else {
unreachable!("The component should be a post-processing middleware")
};
c
}
}
Original file line number Diff line number Diff line change
@@ -1,59 +1,9 @@
use crate::blueprint::conversions::raw_callable2registered_callable;
use crate::blueprint::middleware::{
RegisteredPostProcessingMiddleware, RegisteredWrappingMiddleware,
};
use crate::blueprint::middleware::RegisteredPostProcessingMiddleware;
use crate::blueprint::reflection::RawCallable;
use crate::blueprint::Blueprint;
use pavex_bp_schema::Callable;

/// A middleware that has been configured but has not yet been registered with a [`Blueprint`].
///
/// # Guide
///
/// Check out [`Blueprint::wrap`] for an introduction to wrapping middlewares in Pavex.
///
/// # Use cases
///
/// [`WrappingMiddleware`] is primarily used by
/// [kits](https://pavex.dev/docs/guide/dependency_injection/core_concepts/kits)
/// to allow users to customize (or disable!)
/// the bundled middlewares **before** registering them with a [`Blueprint`].
#[derive(Clone, Debug)]
pub struct WrappingMiddleware {
pub(in crate::blueprint) callable: Callable,
pub(in crate::blueprint) error_handler: Option<Callable>,
}

impl WrappingMiddleware {
/// Create a new (unregistered) wrapping middleware.
///
/// Check out the documentation of [`Blueprint::wrap`] for more details
/// on middleware.
#[track_caller]
pub fn new(callable: RawCallable) -> Self {
Self {
callable: raw_callable2registered_callable(callable),
error_handler: None,
}
}

/// Register an error handler for this middleware.
///
/// Check out the documentation of [`RegisteredWrappingMiddleware::error_handler`] for more details.
#[track_caller]
pub fn error_handler(mut self, error_handler: RawCallable) -> Self {
self.error_handler = Some(raw_callable2registered_callable(error_handler));
self
}

/// Register this middleware with a [`Blueprint`].
///
/// Check out the documentation of [`Blueprint::wrap`] for more details.
pub fn register(self, bp: &mut Blueprint) -> RegisteredWrappingMiddleware {
bp.register_wrapping_middleware(self)
}
}

/// A post-processing middleware that has been configured
/// but has not yet been registered with a [`Blueprint`].
///
Expand Down
5 changes: 5 additions & 0 deletions libs/pavex/src/blueprint/middleware/pre/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod registered;
mod unregistered;

pub use registered::RegisteredPreProcessingMiddleware;
pub use unregistered::PreProcessingMiddleware;
70 changes: 70 additions & 0 deletions libs/pavex/src/blueprint/middleware/pre/registered.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use crate::blueprint::conversions::raw_callable2registered_callable;
use crate::blueprint::reflection::RawCallable;
use pavex_bp_schema::{Blueprint as BlueprintSchema, Component, PreProcessingMiddleware};

/// The type returned by [`Blueprint::pre_process`].
///
/// It allows you to further configure the behaviour of the registered pre-processing
/// middleware.
///
/// [`Blueprint::pre_process`]: crate::blueprint::Blueprint::pre_process
pub struct RegisteredPreProcessingMiddleware<'a> {
pub(crate) blueprint: &'a mut BlueprintSchema,
/// The index of the registered middleware in the blueprint's `components` vector.
pub(crate) component_id: usize,
}

impl<'a> RegisteredPreProcessingMiddleware<'a> {
#[track_caller]
/// Register an error handler.
///
/// If an error handler has already been registered for this middleware, it will be
/// overwritten.
///
/// # Guide
///
/// Check out the ["Error handlers"](https://pavex.dev/docs/guide/errors/error_handlers)
/// section of Pavex's guide for a thorough introduction to error handlers
/// in Pavex applications.
///
/// # Example
///
/// ```rust
/// use pavex::{f, blueprint::Blueprint, middleware::Processing};
/// use pavex::request::RequestHead;
/// use pavex::response::Response;
/// # struct LogLevel;
/// # struct AuthError;
///
/// // 👇 a fallible middleware
/// fn reject_anonymous(request_head: &RequestHead) -> Result<Processing, AuthError>
/// {
/// // [...]
/// # todo!()
/// }
///
/// fn error_to_response(error: &AuthError, log_level: LogLevel) -> Response {
/// // [...]
/// # todo!()
/// }
///
/// # fn main() {
/// let mut bp = Blueprint::new();
/// bp.wrap(f!(crate::reject_anonymous))
/// .error_handler(f!(crate::error_to_response));
/// # }
/// ```
pub fn error_handler(mut self, error_handler: RawCallable) -> Self {
let callable = raw_callable2registered_callable(error_handler);
self.pre_processing_middleware().error_handler = Some(callable);
self
}

fn pre_processing_middleware(&mut self) -> &mut PreProcessingMiddleware {
let component = &mut self.blueprint.components[self.component_id];
let Component::PreProcessingMiddleware(c) = component else {
unreachable!("The component should be a pre-processing middleware")
};
c
}
}
53 changes: 53 additions & 0 deletions libs/pavex/src/blueprint/middleware/pre/unregistered.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use crate::blueprint::conversions::raw_callable2registered_callable;
use crate::blueprint::middleware::pre::RegisteredPreProcessingMiddleware;
use crate::blueprint::reflection::RawCallable;
use crate::blueprint::Blueprint;
use pavex_bp_schema::Callable;

/// A pre-processing middleware that has been configured but has not yet been registered with a [`Blueprint`].
///
/// # Guide
///
/// Check out [`Blueprint::pre_process`] for an introduction to pre_processing middlewares in Pavex.
///
/// # Use cases
///
/// [`PreProcessingMiddleware`] is primarily used by
/// [kits](https://pavex.dev/docs/guide/dependency_injection/core_concepts/kits)
/// to allow users to customize (or disable!)
/// the bundled middlewares **before** registering them with a [`Blueprint`].
#[derive(Clone, Debug)]
pub struct PreProcessingMiddleware {
pub(in crate::blueprint) callable: Callable,
pub(in crate::blueprint) error_handler: Option<Callable>,
}

impl PreProcessingMiddleware {
/// Create a new (unregistered) pre_processing middleware.
///
/// Check out the documentation of [`Blueprint::pre_process`] for more details
/// on pre-processing middlewares.
#[track_caller]
pub fn new(callable: RawCallable) -> Self {
Self {
callable: raw_callable2registered_callable(callable),
error_handler: None,
}
}

/// Register an error handler for this middleware.
///
/// Check out the documentation of [`RegisteredPreProcessingMiddleware::error_handler`] for more details.
#[track_caller]
pub fn error_handler(mut self, error_handler: RawCallable) -> Self {
self.error_handler = Some(raw_callable2registered_callable(error_handler));
self
}

/// Register this middleware with a [`Blueprint`].
///
/// Check out the documentation of [`Blueprint::pre_process`] for more details.
pub fn register(self, bp: &mut Blueprint) -> RegisteredPreProcessingMiddleware {
bp.register_pre_processing_middleware(self)
}
}
Loading

0 comments on commit dbe9683

Please sign in to comment.