Skip to main content

alibi_core/plugin/
routes.rs

1use crate::HttpMethod;
2use std::collections::HashMap;
3/// Generates the [`AuthPlugin`](crate::AuthPlugin) impl for a plugin with static route dispatch.
4///
5/// Eliminates the dual declaration of routes in `routes()` and `on_request()`
6/// by generating both from a single route table.
7///
8/// # Exceptions (must keep manual impl)
9/// - `OAuthPlugin` — dynamic path matching for `/callback/{provider}`
10/// - `SessionManagementPlugin` — match guards and OR patterns
11/// - `EmailPasswordPlugin` — conditional routes based on config
12/// - `UserManagementPlugin` — conditional routes based on config
13/// - `PasswordManagementPlugin` — dynamic path matching for `/reset-password/{token}`
14/// - `OrganizationPlugin` — handlers accept extra `&self.config` argument
15#[macro_export]
16macro_rules! impl_auth_plugin {
17    (@pat get) => { $crate::HttpMethod::Get };
18    (@pat post) => { $crate::HttpMethod::Post };
19    (@pat put) => { $crate::HttpMethod::Put };
20    (@pat delete) => { $crate::HttpMethod::Delete };
21    (@pat patch) => { $crate::HttpMethod::Patch };
22    (@pat head) => { $crate::HttpMethod::Head };
23
24    (@route get) => { $crate::AuthRoute::get };
25    (@route post) => { $crate::AuthRoute::post };
26    (@route put) => { $crate::AuthRoute::put };
27    (@route delete) => { $crate::AuthRoute::delete };
28
29    (
30        $plugin:ty, $name:expr;
31        routes {
32            $( $method:ident $path:literal => $handler:ident, $op_id:literal );* $(;)?
33        }
34        $( extra { $($extra:tt)* } )?
35    ) => {
36        #[::async_trait::async_trait]
37        impl<S: $crate::AuthSchema> $crate::AuthPlugin<S> for $plugin {
38            fn name(&self) -> &'static str { $name }
39
40            fn routes(&self) -> Vec<$crate::AuthRoute> {
41                vec![
42                    $( $crate::AuthRoute::new($crate::impl_auth_plugin!(@pat $method), $path, $op_id), )*
43                ]
44            }
45
46            async fn on_request(
47                &self,
48                req: &$crate::AuthRequest,
49                ctx: &$crate::AuthContext<S>,
50            ) -> $crate::AuthResult<Option<$crate::AuthResponse>> {
51                match (req.method(), req.path()) {
52                    $(
53                        ($crate::impl_auth_plugin!(@pat $method), $path) => {
54                            Ok(Some(self.$handler(req, ctx).await?))
55                        }
56                    )*
57                    _ => Ok(None),
58                }
59            }
60
61            $( $($extra)* )?
62        }
63    };
64}
65
66/// The admitted endpoint's logical callback path and matched path parameters.
67/// Original request bytes and URI remain in the request itself.
68#[derive(Clone, Debug, Default)]
69pub struct ResolvedEndpoint {
70    pub path: String,
71    pub params: HashMap<String, String>,
72}
73
74/// Route definition for plugins.
75#[derive(Debug, Clone)]
76pub struct AuthRoute {
77    pub path: String,
78    /// Logical callback template when it differs from native routing syntax.
79    pub context_path: Option<String>,
80    pub method: HttpMethod,
81    /// Identifier used as the `OpenAPI` `operationId` for this route.
82    pub operation_id: String,
83}
84
85impl AuthRoute {
86    #[must_use]
87    pub fn new(
88        method: HttpMethod,
89        path: impl Into<String>,
90        operation_id: impl Into<String>,
91    ) -> Self {
92        Self {
93            path: path.into(),
94            context_path: None,
95            method,
96            operation_id: operation_id.into(),
97        }
98    }
99
100    #[must_use]
101    pub fn with_context_path(mut self, path: impl Into<String>) -> Self {
102        self.context_path = Some(path.into());
103        self
104    }
105
106    #[must_use]
107    pub fn get(path: impl Into<String>, operation_id: impl Into<String>) -> Self {
108        Self::new(HttpMethod::Get, path, operation_id)
109    }
110
111    #[must_use]
112    pub fn post(path: impl Into<String>, operation_id: impl Into<String>) -> Self {
113        Self::new(HttpMethod::Post, path, operation_id)
114    }
115
116    #[must_use]
117    pub fn put(path: impl Into<String>, operation_id: impl Into<String>) -> Self {
118        Self::new(HttpMethod::Put, path, operation_id)
119    }
120
121    #[must_use]
122    pub fn delete(path: impl Into<String>, operation_id: impl Into<String>) -> Self {
123        Self::new(HttpMethod::Delete, path, operation_id)
124    }
125}