Skip to main content

alibi_core/
hooks.rs

1use crate::types::{AuthRequest, HttpMethod, RequestExtensions, RequestMeta};
2
3/// Request-derived data available to middleware, stores, and other hooks during request handling.
4#[derive(Debug, Clone)]
5pub struct RequestHookContext {
6    /// Original native transport request, retained before endpoint transformations.
7    pub request: AuthRequest,
8    pub method: HttpMethod,
9    pub path: String,
10    /// Original transport URL, including the auth base path and query string.
11    pub url: Option<url::Url>,
12    pub headers: std::collections::HashMap<String, String>,
13    pub query: std::collections::HashMap<String, String>,
14    /// Original request body, available to application lifecycle callbacks.
15    pub body: Option<Vec<u8>>,
16    pub meta: RequestMeta,
17    /// Typed request state shared with the trusted dispatch and its hooks.
18    pub extensions: RequestExtensions,
19}
20
21impl RequestHookContext {
22    /// Build a request hook context from an incoming auth request.
23    #[must_use]
24    pub fn from_request(request: &AuthRequest) -> Self {
25        Self {
26            request: request.clone(),
27            method: request.method().clone(),
28            path: request.path().to_owned(),
29            url: request.url().cloned(),
30            headers: request.headers.clone(),
31            query: request.query.clone(),
32            body: request.body.clone(),
33            meta: RequestMeta::from_request(request),
34            extensions: request.extensions().clone(),
35        }
36    }
37}
38
39tokio::task_local! {
40    static REQUEST_HOOK_CONTEXT: Option<RequestHookContext>;
41}
42
43/// Run a future with request context available to downstream integrations.
44pub async fn with_request_hook_context<T>(
45    request: &AuthRequest,
46    future: impl Future<Output = T>,
47) -> T {
48    with_request_hook_context_value(RequestHookContext::from_request(request), future).await
49}
50
51/// Run a future with an explicit request hook context.
52pub async fn with_request_hook_context_value<T>(
53    request_context: RequestHookContext,
54    future: impl Future<Output = T>,
55) -> T {
56    with_optional_request_hook_context(Some(request_context), future).await
57}
58
59/// Scope an optional actual HTTP context, including absence for a trusted server call.
60pub async fn with_optional_request_hook_context<T>(
61    request_context: Option<RequestHookContext>,
62    future: impl Future<Output = T>,
63) -> T {
64    REQUEST_HOOK_CONTEXT.scope(request_context, future).await
65}
66
67pub fn current_request_hook_context() -> Option<RequestHookContext> {
68    REQUEST_HOOK_CONTEXT.try_with(Clone::clone).ok().flatten()
69}
70
71/// A trusted endpoint's parsed body for adapter callbacks. Completed response
72/// callbacks still receive the original request body.
73#[derive(Clone, Debug)]
74pub struct ValidatedRequestBody(pub crate::utils::json::JsValue);
75
76/// Request body after a plugin has transformed endpoint input. Original HTTP
77/// bytes remain available on `AuthRequest` and `RequestHookContext`.
78#[derive(Clone, Debug)]
79pub struct TransformedRequestBody(pub crate::utils::json::JsValue);
80
81/// Owned native context for application callbacks, including background delivery.
82/// The schema accessor exposes the live instance and its hook-aware store; the
83/// request and hook data retain the actual transport and admitted endpoint input.
84#[derive(Clone)]
85pub struct CallbackContext {
86    pub request: Option<AuthRequest>,
87    pub request_hook: Option<RequestHookContext>,
88    pub endpoint: Option<crate::endpoint::EndpointCall>,
89    context: std::sync::Arc<dyn std::any::Any + Send + Sync>,
90}
91
92impl CallbackContext {
93    #[must_use]
94    pub fn new<S: crate::AuthSchema>(
95        context: &crate::AuthContext<S>,
96        request: Option<&AuthRequest>,
97    ) -> Self {
98        let endpoint = crate::endpoint::current_endpoint_call_context();
99        let request_hook = current_request_hook_context();
100        Self {
101            request: request
102                .or_else(|| {
103                    endpoint
104                        .as_ref()
105                        .and_then(crate::endpoint::EndpointCall::request)
106                })
107                .or_else(|| request_hook.as_ref().map(|hook| &hook.request))
108                .cloned(),
109            request_hook,
110            endpoint,
111            context: std::sync::Arc::new(context.clone()),
112        }
113    }
114
115    /// Access the actual initialized instance using the application's schema.
116    #[must_use]
117    pub fn context<S: crate::AuthSchema>(&self) -> Option<&crate::AuthContext<S>> {
118        self.context.downcast_ref()
119    }
120}