From b07b84d7360e7fe21f636229a0e63163eb860d7a Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Wed, 3 Jan 2024 20:45:27 -0500 Subject: [PATCH] automatically include server function handler in `.leptos_router()` --- examples/todo_app_sqlite_axum/Todos.db | Bin 16384 -> 16384 bytes examples/todo_app_sqlite_axum/src/main.rs | 1 - integrations/axum/src/lib.rs | 30 +++++++++++++++-- server_fn/Cargo.toml | 5 ++- server_fn/src/codec/cbor.rs | 2 ++ server_fn/src/codec/json.rs | 2 ++ server_fn/src/codec/mod.rs | 2 ++ server_fn/src/codec/multipart.rs | 2 ++ server_fn/src/codec/rkyv.rs | 2 ++ server_fn/src/codec/stream.rs | 3 ++ server_fn/src/codec/url.rs | 3 ++ server_fn/src/lib.rs | 39 ++++++++++++++++------ server_fn_macro/src/lib.rs | 3 +- 13 files changed, 77 insertions(+), 17 deletions(-) diff --git a/examples/todo_app_sqlite_axum/Todos.db b/examples/todo_app_sqlite_axum/Todos.db index 263ef90a1294c8770642036723c764eba4591d78..536e991ee4b5fb5365ee0ded31402ceaaf029a99 100644 GIT binary patch delta 79 zcmZo@U~Fh$oFL7pFj2;tQDI}kLViL1mkccYH(B_9@;~7J#DA0j=4L^I&HR&(%8N*| g@~|-Qb2zhdf+&{~MFs{2b|By0o|Sv^7kM590E^EQ5C8xG delta 79 zcmZo@U~Fh$oFL68Gf~EwQD$SpLViI81_l=X2Q2(Q`9JYL;D5>ga } ) .fallback(file_and_error_handler) diff --git a/integrations/axum/src/lib.rs b/integrations/axum/src/lib.rs index 956de2bd4e..05321f55aa 100644 --- a/integrations/axum/src/lib.rs +++ b/integrations/axum/src/lib.rs @@ -39,11 +39,10 @@ use axum::{ http::{ header::{self, HeaderName, HeaderValue}, request::Parts, - HeaderMap, Request, Response, StatusCode, + HeaderMap, Method, Request, Response, StatusCode, }, response::IntoResponse, routing::{delete, get, patch, post, put}, - RequestPartsExt, }; use futures::{ channel::mpsc::{Receiver, Sender}, @@ -1540,6 +1539,8 @@ where IV: IntoView + 'static, { let mut router = self; + + // register router paths for listing in paths.iter() { let path = listing.path(); @@ -1631,6 +1632,31 @@ where }; } } + + // register server functions + for (path, method) in server_fn::axum::server_fn_paths() { + let additional_context = additional_context.clone(); + let handler = move |req: Request| async move { + handle_server_fns_with_context(additional_context, req).await + }; + router = router.route( + path, + match method { + Method::GET => get(handler), + Method::POST => post(handler), + Method::PUT => put(handler), + Method::DELETE => delete(handler), + Method::PATCH => patch(handler), + _ => { + panic!( + "Unsupported server function HTTP method: \ + {method:?}" + ); + } + }, + ); + } + router } diff --git a/server_fn/Cargo.toml b/server_fn/Cargo.toml index 8fb8edca25..498443cfc4 100644 --- a/server_fn/Cargo.toml +++ b/server_fn/Cargo.toml @@ -34,7 +34,7 @@ multer = { version = "3", optional = true } # serde serde_json = "1" futures = "0.3" -http = { version = "1", optional = true } +http = { version = "1" } ciborium = { version = "0.2", optional = true } hyper = { version = "1", optional = true } bytes = "1" @@ -67,7 +67,6 @@ reqwest = { version = "0.11", default-features = false, optional = true, feature actix = ["dep:actix-web", "dep:send_wrapper"] axum = [ "dep:axum", - "dep:http", "dep:hyper", "dep:http-body-util", "dep:tower", @@ -88,5 +87,5 @@ cbor = ["dep:ciborium"] rkyv = ["dep:rkyv"] default-tls = ["reqwest/default-tls"] rustls = ["reqwest/rustls-tls"] -reqwest = ["dep:http", "dep:reqwest"] +reqwest = ["dep:reqwest"] ssr = ["inventory"] diff --git a/server_fn/src/codec/cbor.rs b/server_fn/src/codec/cbor.rs index e9acb50f56..9f0e8d6ab7 100644 --- a/server_fn/src/codec/cbor.rs +++ b/server_fn/src/codec/cbor.rs @@ -5,6 +5,7 @@ use crate::{ response::{ClientRes, Res}, }; use bytes::Bytes; +use http::Method; use serde::{de::DeserializeOwned, Serialize}; /// Pass arguments and receive responses using `cbor` in a `POST` request. @@ -12,6 +13,7 @@ pub struct Cbor; impl Encoding for Cbor { const CONTENT_TYPE: &'static str = "application/cbor"; + const METHOD: Method = Method::POST; } impl IntoReq for T diff --git a/server_fn/src/codec/json.rs b/server_fn/src/codec/json.rs index f90f420a6d..0dcb062d21 100644 --- a/server_fn/src/codec/json.rs +++ b/server_fn/src/codec/json.rs @@ -5,12 +5,14 @@ use crate::{ response::{ClientRes, Res}, IntoReq, IntoRes, }; +use http::Method; use serde::{de::DeserializeOwned, Serialize}; /// Pass arguments and receive responses as JSON in the body of a `POST` request. pub struct Json; impl Encoding for Json { const CONTENT_TYPE: &'static str = "application/json"; + const METHOD: Method = Method::POST; } impl IntoReq for T diff --git a/server_fn/src/codec/mod.rs b/server_fn/src/codec/mod.rs index 9fba70edf5..426f24b6b6 100644 --- a/server_fn/src/codec/mod.rs +++ b/server_fn/src/codec/mod.rs @@ -4,6 +4,7 @@ mod cbor; pub use cbor::*; #[cfg(feature = "json")] mod json; +use http::Method; #[cfg(feature = "json")] pub use json::*; #[cfg(feature = "rkyv")] @@ -59,6 +60,7 @@ pub trait IntoRes { pub trait Encoding { const CONTENT_TYPE: &'static str; + const METHOD: Method; } pub trait FormDataEncoding diff --git a/server_fn/src/codec/multipart.rs b/server_fn/src/codec/multipart.rs index aa11fcd6f6..a3c254e6ed 100644 --- a/server_fn/src/codec/multipart.rs +++ b/server_fn/src/codec/multipart.rs @@ -5,6 +5,7 @@ use crate::{ IntoReq, }; use futures::StreamExt; +use http::Method; use multer::Multipart; use web_sys::FormData; @@ -12,6 +13,7 @@ pub struct MultipartFormData; impl Encoding for MultipartFormData { const CONTENT_TYPE: &'static str = "multipart/form-data"; + const METHOD: Method = Method::POST; } #[derive(Debug)] diff --git a/server_fn/src/codec/rkyv.rs b/server_fn/src/codec/rkyv.rs index 02c4aae2aa..17435d342b 100644 --- a/server_fn/src/codec/rkyv.rs +++ b/server_fn/src/codec/rkyv.rs @@ -5,6 +5,7 @@ use crate::{ response::{ClientRes, Res}, }; use bytes::Bytes; +use http::Method; use rkyv::{ de::deserializers::SharedDeserializeMap, ser::serializers::AllocSerializer, validation::validators::DefaultValidator, Archive, CheckBytes, Deserialize, @@ -16,6 +17,7 @@ pub struct Rkyv; impl Encoding for Rkyv { const CONTENT_TYPE: &'static str = "application/rkyv"; + const METHOD: Method = Method::POST; } impl IntoReq for T diff --git a/server_fn/src/codec/stream.rs b/server_fn/src/codec/stream.rs index 8158261ac4..020bfdb546 100644 --- a/server_fn/src/codec/stream.rs +++ b/server_fn/src/codec/stream.rs @@ -6,12 +6,14 @@ use crate::{ }; use bytes::Bytes; use futures::{Stream, StreamExt}; +use http::Method; use std::pin::Pin; pub struct Streaming; impl Encoding for Streaming { const CONTENT_TYPE: &'static str = "application/octet-stream"; + const METHOD: Method = Method::POST; } /* impl IntoReq for T @@ -81,6 +83,7 @@ pub struct StreamingText; impl Encoding for StreamingText { const CONTENT_TYPE: &'static str = "text/plain"; + const METHOD: Method = Method::POST; } pub struct TextStream( diff --git a/server_fn/src/codec/url.rs b/server_fn/src/codec/url.rs index e133066e28..e75953c6ad 100644 --- a/server_fn/src/codec/url.rs +++ b/server_fn/src/codec/url.rs @@ -3,6 +3,7 @@ use crate::{ error::ServerFnError, request::{ClientReq, Req}, }; +use http::Method; use serde::{de::DeserializeOwned, Serialize}; /// Pass arguments as a URL-encoded query string of a `GET` request. @@ -13,6 +14,7 @@ pub struct PostUrl; impl Encoding for GetUrl { const CONTENT_TYPE: &'static str = "application/x-www-form-urlencoded"; + const METHOD: Method = Method::GET; } impl IntoReq for T @@ -46,6 +48,7 @@ where impl Encoding for PostUrl { const CONTENT_TYPE: &'static str = "application/x-www-form-urlencoded"; + const METHOD: Method = Method::POST; } impl IntoReq for T diff --git a/server_fn/src/lib.rs b/server_fn/src/lib.rs index 68c712dbf6..2078af8f4c 100644 --- a/server_fn/src/lib.rs +++ b/server_fn/src/lib.rs @@ -20,6 +20,7 @@ pub use const_format; use dashmap::DashMap; pub use error::ServerFnError; use error::ServerFnErrorSerde; +use http::Method; use middleware::{Layer, Service}; use once_cell::sync::Lazy; use request::Req; @@ -155,7 +156,7 @@ macro_rules! initialize_server_fn_map { once_cell::sync::Lazy::new(|| { $crate::inventory::iter::> .into_iter() - .map(|obj| (obj.path(), *obj)) + .map(|obj| (obj.path(), obj.clone())) .collect() }) }; @@ -163,6 +164,7 @@ macro_rules! initialize_server_fn_map { pub struct ServerFnTraitObj { path: &'static str, + method: Method, handler: fn(Req) -> Pin + Send>>, middleware: fn() -> Vec>>, } @@ -170,11 +172,13 @@ pub struct ServerFnTraitObj { impl ServerFnTraitObj { pub const fn new( path: &'static str, + method: Method, handler: fn(Req) -> Pin + Send>>, middleware: fn() -> Vec>>, ) -> Self { Self { path, + method, handler, middleware, } @@ -183,6 +187,10 @@ impl ServerFnTraitObj { pub fn path(&self) -> &'static str { self.path } + + pub fn method(&self) -> Method { + self.method.clone() + } } impl Service for ServerFnTraitObj @@ -198,12 +206,15 @@ where impl Clone for ServerFnTraitObj { fn clone(&self) -> Self { - *self + Self { + path: self.path, + method: self.method.clone(), + handler: self.handler, + middleware: self.middleware, + } } } -impl Copy for ServerFnTraitObj {} - type LazyServerFnMap = Lazy>>; @@ -212,10 +223,10 @@ type LazyServerFnMap = pub mod axum { use crate::{ middleware::{BoxedService, Layer, Service}, - LazyServerFnMap, ServerFn, ServerFnTraitObj, + Encoding, LazyServerFnMap, ServerFn, ServerFnTraitObj, }; use axum::body::Body; - use http::{Request, Response, StatusCode}; + use http::{Method, Request, Response, StatusCode}; inventory::collect!(ServerFnTraitObj, Response>); @@ -235,12 +246,19 @@ pub mod axum { T::PATH, ServerFnTraitObj::new( T::PATH, + T::InputEncoding::METHOD, |req| Box::pin(T::run_on_server(req)), T::middlewares, ), ); } + pub fn server_fn_paths() -> impl Iterator { + REGISTERED_SERVER_FUNCTIONS + .iter() + .map(|item| (item.path(), item.method())) + } + pub async fn handle_server_fn(req: Request) -> Response { let path = req.uri().path(); @@ -268,7 +286,7 @@ pub mod axum { ) -> Option, Response>> { REGISTERED_SERVER_FUNCTIONS.get(path).map(|server_fn| { let middleware = (server_fn.middleware)(); - let mut service = BoxedService::new(*server_fn); + let mut service = BoxedService::new(server_fn.clone()); for middleware in middleware { service = middleware.layer(service); } @@ -282,11 +300,10 @@ pub mod axum { pub mod actix { use crate::{ middleware::BoxedService, request::actix::ActixRequest, - response::actix::ActixResponse, LazyServerFnMap, ServerFn, + response::actix::ActixResponse, Encoding, LazyServerFnMap, ServerFn, ServerFnTraitObj, }; use actix_web::{HttpRequest, HttpResponse}; - use send_wrapper::SendWrapper; inventory::collect!(ServerFnTraitObj); @@ -306,6 +323,7 @@ pub mod actix { T::PATH, ServerFnTraitObj::new( T::PATH, + T::InputEncoding::METHOD, |req| Box::pin(T::run_on_server(req)), T::middlewares, ), @@ -316,7 +334,8 @@ pub mod actix { let path = req.uri().path(); if let Some(server_fn) = REGISTERED_SERVER_FUNCTIONS.get(path) { let middleware = (server_fn.middleware)(); - let mut service = BoxedService::new(*server_fn); + // 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); } diff --git a/server_fn_macro/src/lib.rs b/server_fn_macro/src/lib.rs index d0b0633389..9418c9bbf6 100644 --- a/server_fn_macro/src/lib.rs +++ b/server_fn_macro/src/lib.rs @@ -237,9 +237,10 @@ pub fn server_macro_impl( let inventory = if cfg!(feature = "ssr") { quote! { #server_fn_path::inventory::submit! {{ - use #server_fn_path::ServerFn; + use #server_fn_path::{ServerFn, codec::Encoding}; #server_fn_path::ServerFnTraitObj::new( #struct_name::PATH, + <#struct_name as ServerFn>::InputEncoding::METHOD, |req| { Box::pin(#struct_name::run_on_server(req)) },