nest_rs_http/shaper.rs
1//! Per-route, type-directed response shaping.
2//!
3//! `#[routes]` detects a handler parameter naming a [`RouteResponseShaper`]
4//! (in practice the `Authorize<_, _>` gate) and wraps the handler with
5//! [`shaped`]. The shaper sits *inside* the route's guards — a guard that
6//! attached request context (the authorization ability) has already run when
7//! `capture` reads it. `run` then wraps the handler future, so the shaper may
8//! both install ambient state around the handler and rewrite its response.
9//!
10//! The trait is implemented outside this crate (`nest_rs_authz::http`) so the
11//! HTTP surface stays unaware of any specific shaper.
12
13use std::future::Future;
14use std::marker::PhantomData;
15
16use poem::{Endpoint, IntoResponse, Request, Response, Result};
17
18/// A handler wrapper keyed by a marker type. `#[routes]` applies it when a
19/// handler declares a parameter of an implementing type.
20pub trait RouteResponseShaper {
21 /// Bits the shaper extracts from the request before the handler consumes it.
22 type Captured: Send;
23
24 fn capture(req: &Request) -> Self::Captured;
25
26 /// Run the handler `inner` and shape its result. The shaper may wrap
27 /// `inner` to install ambient state for its duration and may transform the
28 /// response before returning it.
29 fn run<F>(captured: Self::Captured, inner: F) -> impl Future<Output = Result<Response>> + Send
30 where
31 F: Future<Output = Result<Response>> + Send;
32}
33
34pub fn shaped<P, E>(inner: E, _shaper: PhantomData<P>) -> ShapedEndpoint<P, E> {
35 ShapedEndpoint {
36 inner,
37 _marker: PhantomData,
38 }
39}
40
41pub struct ShapedEndpoint<P, E> {
42 inner: E,
43 _marker: PhantomData<fn() -> P>,
44}
45
46impl<P, E> Endpoint for ShapedEndpoint<P, E>
47where
48 P: RouteResponseShaper + Send + Sync + 'static,
49 E: Endpoint + Send + Sync,
50 E::Output: IntoResponse,
51{
52 type Output = Response;
53
54 async fn call(&self, req: Request) -> Result<Response> {
55 let captured = P::capture(&req);
56 let inner = async move { self.inner.call(req).await.map(IntoResponse::into_response) };
57 P::run(captured, inner).await
58 }
59}