Skip to main content

better_auth_core/plugin/
routes.rs

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