Skip to main content

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}