feat: allow reusing the same endpoint for server functions with different HTTP verbs in their input encodings

This commit is contained in:
Greg Johnston 2024-07-30 10:16:34 -04:00
parent 24775fb59b
commit 4fa72a94fb
3 changed files with 46 additions and 24 deletions

View File

@ -302,8 +302,9 @@ pub fn handle_server_fns_with_context(
let additional_context = additional_context.clone(); let additional_context = additional_context.clone();
let path = req.path(); let path = req.path();
let method = req.method();
if let Some(mut service) = if let Some(mut service) =
server_fn::actix::get_server_fn_service(path) server_fn::actix::get_server_fn_service(path, method)
{ {
let owner = Owner::new(); let owner = Owner::new();
owner owner
@ -1323,7 +1324,7 @@ impl LeptosRoutes for &mut ServiceConfig {
let mut router = self; let mut router = self;
// register server functions first to allow for wildcard route in Leptos's Router // register server functions first to allow for wildcard route in Leptos's Router
for (path, _) in server_fn::actix::server_fn_paths() { for (path, method) in server_fn::actix::server_fn_paths() {
let additional_context = additional_context.clone(); let additional_context = additional_context.clone();
let handler = handle_server_fns_with_context(additional_context); let handler = handle_server_fns_with_context(additional_context);
router = router.route(path, handler); router = router.route(path, handler);

View File

@ -313,10 +313,13 @@ async fn handle_server_fns_inner(
) -> impl IntoResponse { ) -> impl IntoResponse {
use server_fn::middleware::Service; use server_fn::middleware::Service;
let method = req.method().clone();
let path = req.uri().path().to_string(); let path = req.uri().path().to_string();
let (req, parts) = generate_request_and_parts(req); let (req, parts) = generate_request_and_parts(req);
if let Some(mut service) = server_fn::axum::get_server_fn_service(&path) { if let Some(mut service) =
server_fn::axum::get_server_fn_service(&path, method)
{
let owner = Owner::new(); let owner = Owner::new();
owner owner
.with(|| { .with(|| {

View File

@ -362,7 +362,9 @@ macro_rules! initialize_server_fn_map {
once_cell::sync::Lazy::new(|| { once_cell::sync::Lazy::new(|| {
$crate::inventory::iter::<ServerFnTraitObj<$req, $res>> $crate::inventory::iter::<ServerFnTraitObj<$req, $res>>
.into_iter() .into_iter()
.map(|obj| (obj.path(), obj.clone())) .map(|obj| {
((obj.path().to_string(), obj.method()), obj.clone())
})
.collect() .collect()
}) })
}; };
@ -442,7 +444,7 @@ impl<Req, Res> Clone for ServerFnTraitObj<Req, Res> {
#[allow(unused)] // used by server integrations #[allow(unused)] // used by server integrations
type LazyServerFnMap<Req, Res> = type LazyServerFnMap<Req, Res> =
Lazy<DashMap<&'static str, ServerFnTraitObj<Req, Res>>>; Lazy<DashMap<(String, Method), ServerFnTraitObj<Req, Res>>>;
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
impl<Req: 'static, Res: 'static> inventory::Collect impl<Req: 'static, Res: 'static> inventory::Collect
@ -481,7 +483,7 @@ pub mod axum {
> + 'static, > + 'static,
{ {
REGISTERED_SERVER_FUNCTIONS.insert( REGISTERED_SERVER_FUNCTIONS.insert(
T::PATH, (T::PATH.into(), T::InputEncoding::METHOD),
ServerFnTraitObj::new( ServerFnTraitObj::new(
T::PATH, T::PATH,
T::InputEncoding::METHOD, T::InputEncoding::METHOD,
@ -502,7 +504,9 @@ pub mod axum {
pub async fn handle_server_fn(req: Request<Body>) -> Response<Body> { pub async fn handle_server_fn(req: Request<Body>) -> Response<Body> {
let path = req.uri().path(); let path = req.uri().path();
if let Some(mut service) = get_server_fn_service(path) { if let Some(mut service) =
get_server_fn_service(path, req.method().clone())
{
service.run(req).await service.run(req).await
} else { } else {
Response::builder() Response::builder()
@ -524,8 +528,10 @@ pub mod axum {
/// Returns the server function at the given path as a service that can be modified. /// Returns the server function at the given path as a service that can be modified.
pub fn get_server_fn_service( pub fn get_server_fn_service(
path: &str, path: &str,
method: Method,
) -> Option<BoxedService<Request<Body>, Response<Body>>> { ) -> Option<BoxedService<Request<Body>, Response<Body>>> {
REGISTERED_SERVER_FUNCTIONS.get(path).map(|server_fn| { let key = (path.into(), method);
REGISTERED_SERVER_FUNCTIONS.get(&key).map(|server_fn| {
let middleware = (server_fn.middleware)(); let middleware = (server_fn.middleware)();
let mut service = BoxedService::new(server_fn.clone()); let mut service = BoxedService::new(server_fn.clone());
for middleware in middleware { for middleware in middleware {
@ -565,7 +571,7 @@ pub mod actix {
> + 'static, > + 'static,
{ {
REGISTERED_SERVER_FUNCTIONS.insert( REGISTERED_SERVER_FUNCTIONS.insert(
T::PATH, (T::PATH.into(), T::InputEncoding::METHOD),
ServerFnTraitObj::new( ServerFnTraitObj::new(
T::PATH, T::PATH,
T::InputEncoding::METHOD, T::InputEncoding::METHOD,
@ -588,13 +594,8 @@ pub mod actix {
payload: Payload, payload: Payload,
) -> HttpResponse { ) -> HttpResponse {
let path = req.uri().path(); let path = req.uri().path();
if let Some(server_fn) = REGISTERED_SERVER_FUNCTIONS.get(path) { let method = req.method();
let middleware = (server_fn.middleware)(); if let Some(mut service) = get_server_fn_service(path, method) {
// http::Method is the only non-Copy type here
let mut service = BoxedService::new(server_fn.clone());
for middleware in middleware {
service = middleware.layer(service);
}
service service
.0 .0
.run(ActixRequest::from((req, payload))) .run(ActixRequest::from((req, payload)))
@ -618,14 +619,31 @@ pub mod actix {
/// Returns the server function at the given path as a service that can be modified. /// Returns the server function at the given path as a service that can be modified.
pub fn get_server_fn_service( pub fn get_server_fn_service(
path: &str, path: &str,
method: &actix_web::http::Method,
) -> Option<BoxedService<ActixRequest, ActixResponse>> { ) -> Option<BoxedService<ActixRequest, ActixResponse>> {
REGISTERED_SERVER_FUNCTIONS.get(path).map(|server_fn| { use actix_web::http::Method as ActixMethod;
let middleware = (server_fn.middleware)();
let mut service = BoxedService::new(server_fn.clone()); let method = match *method {
for middleware in middleware { ActixMethod::GET => Method::GET,
service = middleware.layer(service); ActixMethod::POST => Method::POST,
} ActixMethod::PUT => Method::PUT,
service ActixMethod::PATCH => Method::PATCH,
}) ActixMethod::DELETE => Method::DELETE,
ActixMethod::HEAD => Method::HEAD,
ActixMethod::TRACE => Method::TRACE,
ActixMethod::OPTIONS => Method::OPTIONS,
ActixMethod::CONNECT => Method::CONNECT,
_ => unreachable!(),
};
REGISTERED_SERVER_FUNCTIONS.get(&(path.into(), method)).map(
|server_fn| {
let middleware = (server_fn.middleware)();
let mut service = BoxedService::new(server_fn.clone());
for middleware in middleware {
service = middleware.layer(service);
}
service
},
)
} }
} }