Skip to main content

nest_rs_guards/
lib.rs

1//! # nest-rs-guards
2//!
3//! Transport-spanning guards for nestrs — **one trait, three transports**
4//! (HTTP, GraphQL, WS). Declared once with
5//! `App::builder().use_guards_global(...)`, every handler on every
6//! transport runs through the chain.
7//!
8//! Plug-in point for the Layer System: every guard is a [`Layer`], so the
9//! `#[routes]` / `#[resolver]` / `#[messages]` shapers dedup by `TypeId` when
10//! the same guard is declared at multiple sites (global + controller +
11//! method) — the broadest [`LayerSite`](nest_rs_core::LayerSite) wins and the
12//! rest log a `warn`. The framework runs guards in **declaration order**;
13//! [`Layer::priority`] is an opt-in tiebreaker.
14//!
15//! `#[public]` is not a framework-level skip: the macro attaches a
16//! [`Public`](nest_rs_core::Public) marker via the same metadata channel
17//! as `#[meta(...)]`, and each guard decides whether to honor it. An
18//! `AbilityGuard` may still run on a public route to apply visitor rules;
19//! an `AuthGuard` may skip rejection when no token is present.
20//!
21//! ## Defining a guard
22//!
23//! Override only the `check_*` method(s) where this guard has work to do —
24//! the rest inherit `Ok(())` defaults. `Layer` provides `priority()` /
25//! `name()` defaults; override `priority()` only when this guard must beat
26//! declaration order.
27//!
28//! ```rust,ignore
29//! use nest_rs_guards::prelude::*;
30//!
31//! #[injectable]
32//! #[derive(Default)]
33//! pub struct AuditGuard;
34//!
35//! impl Layer for AuditGuard {}
36//!
37//! #[async_trait]
38//! impl Guard for AuditGuard {
39//!     async fn check_http(&self, req: &mut HttpRequest) -> Result<(), Denial> {
40//!         tracing::info!(target: "audit", method = %req.method(), path = %req.uri(), "request");
41//!         Ok(())
42//!     }
43//! }
44//! ```
45//!
46//! ## Registering globally
47//!
48//! ```rust,ignore
49//! use nest_rs::App;
50//! use nest_rs_guards::{AppBuilderGuardsExt, guard};
51//!
52//! App::builder()
53//!     .use_guards_global([guard::<AuthGuard>(), guard::<AuthzGuard>()])
54//!     .module::<AppModule>()
55//!     .build().await?
56//!     .run().await
57//! ```
58//!
59//! Declaration order is the runtime order. If you list `AuthzGuard` before
60//! `AuthGuard` the authorization check runs against an empty principal — a
61//! name-based heuristic logs a `warn` at boot.
62//!
63//! ## Marking a handler `#[public]`
64//!
65//! ```rust,ignore
66//! #[get("/health/live")]
67//! #[public]
68//! async fn live() -> &'static str { "ok" }
69//! ```
70//!
71//! The macro attaches a [`Public`](nest_rs_core::Public) marker to the
72//! route. Guards that want to honor it read it via the transport's
73//! reflector and adjust their policy.
74//!
75//! ## Architecture
76//!
77//! Each shaper macro (`#[routes]`, `#[resolver]`, `#[messages]`) emits a
78//! call to one of [`RouteShaper`] / [`run_layered_graphql_chain`]
79//! / [`run_layered_ws_chain`] at the start of every handler — the per-route
80//! entry is what gives TypeId-level dedup against the global chain. Guards
81//! have **no** transport-edge band: the pool executes in the shaper
82//! (post-routing, so it reads `#[public]`), at a `Guarded` self-mount's
83//! edge (`SelfMountGuardWrap`), or in-band on `/graphql` (the
84//! [`GlobalPoolOperationGuard`](dispatch::GlobalPoolOperationGuard)
85//! fallback when no bridge is registered).
86//!
87//! **Larger than its siblings on purpose.** Where `nest-rs-interceptors` /
88//! `nest-rs-filters` / `nest-rs-exception-filters` each carry only their own
89//! trait + a builder + a registry, this crate also owns the cross-transport
90//! [`dispatch`] machinery (the [`RouteShaper`] entry, the layer-chain helpers,
91//! the graphql/ws chain runners) that the other three trio members consume.
92//! Splitting it would mean duplicating the chain across crates or routing
93//! through a fifth — both worse than the asymmetry.
94
95mod builder;
96mod denial;
97pub mod dispatch;
98mod endpoint;
99mod guard;
100pub mod prelude;
101mod registry;
102
103pub use builder::{AppBuilderGuardsExt, AppBuilderPipesExt};
104// Cross-transport interceptor / filter / exception-filter trait methods
105// (`wrap_graphql`, `wrap_ws`, `filter_graphql`, `filter_ws`,
106// `catch_graphql`, `catch_ws`) and the matching continuation types
107// (`GraphqlNext`, `WsNext`) now live directly on the base traits in
108// `nest-rs-interceptors` / `nest-rs-filters` / `nest-rs-exception-filters`.
109// Re-exported here for the historical import path used by the macros.
110pub use denial::Denial;
111pub use endpoint::{GuardEndpoint, GuardExt};
112pub use guard::{Guard, GuardAsWsMessageCheck};
113// The dedup logic itself lives in `nest_rs_core::layer_chain` — the single
114// home every execution site (route shaper, transport pool folds, graphql/ws
115// in-band chains) composes through. Re-exported for macro-emitted code.
116pub use nest_rs_core::layer_chain;
117pub use nest_rs_core::layer_chain::{LayerSite, ResolvedLayer};
118pub use nest_rs_interceptors::{GraphqlNext, WsNext};
119pub use registry::{GuardSpec, GuardSpecs, PipeSpec, PipeSpecs, guard, pipe};
120
121// Re-export dispatch helpers for macro-emitted code.
122pub use dispatch::{
123    RouteShaper, denial_to_graphql_error, denial_to_http_response, run_layered_graphql_chain,
124    run_layered_ws_chain,
125};