Skip to main content

Crate nest_rs_guards

Crate nest_rs_guards 

Source
Expand description

§nest-rs-guards

Transport-spanning guards for nestrs — one trait, three transports (HTTP, GraphQL, WS). Declared once with App::builder().use_guards_global(...), every handler on every transport runs through the chain.

Plug-in point for the Layer System: every guard is a [Layer], so the #[routes] / #[resolver] / #[messages] shapers dedup by TypeId when the same guard is declared at multiple sites (global + controller + method) — the broadest LayerSite wins and the rest log a warn. The framework runs guards in declaration order; [Layer::priority] is an opt-in tiebreaker.

#[public] is not a framework-level skip: the macro attaches a Public marker via the same metadata channel as #[meta(...)], and each guard decides whether to honor it. An AbilityGuard may still run on a public route to apply visitor rules; an AuthGuard may skip rejection when no token is present.

§Defining a guard

Override only the check_* method(s) where this guard has work to do — the rest inherit Ok(()) defaults. Layer provides priority() / name() defaults; override priority() only when this guard must beat declaration order.

use nest_rs_guards::prelude::*;

#[injectable]
#[derive(Default)]
pub struct AuditGuard;

impl Layer for AuditGuard {}

#[async_trait]
impl Guard for AuditGuard {
    async fn check_http(&self, req: &mut HttpRequest) -> Result<(), Denial> {
        tracing::info!(target: "audit", method = %req.method(), path = %req.uri(), "request");
        Ok(())
    }
}

§Registering globally

use nest_rs::App;
use nest_rs_guards::{AppBuilderGuardsExt, guard};

App::builder()
    .use_guards_global([guard::<AuthGuard>(), guard::<AuthzGuard>()])
    .module::<AppModule>()
    .build().await?
    .run().await

Declaration order is the runtime order. If you list AuthzGuard before AuthGuard the authorization check runs against an empty principal — a name-based heuristic logs a warn at boot.

§Marking a handler #[public]

#[get("/health/live")]
#[public]
async fn live() -> &'static str { "ok" }

The macro attaches a Public marker to the route. Guards that want to honor it read it via the transport’s reflector and adjust their policy.

§Architecture

Each shaper macro (#[routes], #[resolver], #[messages]) emits a call to one of RouteShaper / run_layered_graphql_chain / run_layered_ws_chain at the start of every handler — the per-route entry is what gives TypeId-level dedup against the global chain. Guards have no transport-edge band: the pool executes in the shaper (post-routing, so it reads #[public]), at a Guarded self-mount’s edge (SelfMountGuardWrap), or in-band on /graphql (the GlobalPoolOperationGuard fallback when no bridge is registered).

Larger than its siblings on purpose. Where nest-rs-interceptors / nest-rs-filters / nest-rs-exception-filters each carry only their own trait + a builder + a registry, this crate also owns the cross-transport dispatch machinery (the RouteShaper entry, the layer-chain helpers, the graphql/ws chain runners) that the other three trio members consume. Splitting it would mean duplicating the chain across crates or routing through a fifth — both worse than the asymmetry.

Re-exports§

pub use dispatch::RouteShaper;
pub use dispatch::denial_to_graphql_error;
pub use dispatch::denial_to_http_response;
pub use dispatch::run_layered_graphql_chain;
pub use dispatch::run_layered_ws_chain;

Modules§

dispatch
Runtime dispatch of the Layer System chain — the helpers and types the three shaper macros emit at the start of every handler.
layer_chain
Layer chain composition — the dedup-by-TypeId logic shared by every execution site of the Layer System (the per-route shaper, the transport pool folds, the GraphQL / WS in-band chains).
prelude
Re-export everything a custom guard implementation needs in one use.

Structs§

GuardAsWsMessageCheck
Newtype adapter that lets any Guard satisfy the WsMessageCheck interface — the bridge the #[messages] macro uses to put guards in the per-event chain table without nest-rs-ws depending on nest-rs-guards.
GuardEndpoint
Wraps any poem endpoint with a Guard’s check_http step.
GuardSpec
One entry in the use_guards_global list. Created by guard::<T>(); resolved against the live container at configure time.
GuardSpecs
The unresolved Vec<GuardSpec> seeded into the container by AppBuilder::use_guards_global(...). Each transport reads it at configure time and resolves against the live container.
PipeSpec
One entry in the use_pipes_global list — same shape as GuardSpec.
PipeSpecs
The unresolved Vec<PipeSpec> seeded by AppBuilder::use_pipes_global.
ResolvedLayer
A layer that survived dedup, paired with its origin site and the name the shaper logged at mount.

Enums§

Denial
What a Guard returns on rejection.
LayerSite
Where a layer was declared. Used by the dedup logic — when the same TypeId appears at several sites, the broadest site wins because a wider declaration signals “this must run everywhere — don’t bypass it locally”.

Traits§

AppBuilderGuardsExt
Adds .use_guards_global(...) to AppBuilder.
AppBuilderPipesExt
Adds .use_pipes_global(...) to AppBuilder. Each pipe runs before every JSON HTTP handler; per-route opt-out via #[no_pipes].
Guard
A transport-spanning guard.
GuardExt
Extension trait so a poem endpoint can chain .guard(arc_guard).

Functions§

guard
Construct a GuardSpec for the given guard type.
pipe
Construct a PipeSpec for the given pipe type.

Type Aliases§

GraphqlNext
Continuation passed to Interceptor::wrap_graphql. .await invokes the next interceptor in the chain (or the resolver itself when this is the innermost wrap).
WsNext
Continuation passed to Interceptor::wrap_ws. .await invokes the next interceptor in the chain (or the message handler itself when this is the innermost wrap).