mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 06:44:17 +00:00
feat: make struct name and path optional for server functions (#1573)
This commit is contained in:
parent
b98174db7a
commit
7306ecccbc
10 changed files with 143 additions and 32 deletions
|
@ -15,13 +15,12 @@ cfg_if! {
|
|||
}
|
||||
}
|
||||
|
||||
// "/api" is an optional prefix that allows you to locate server functions wherever you'd like on the server
|
||||
#[server(GetServerCount, "/api")]
|
||||
#[server]
|
||||
pub async fn get_server_count() -> Result<i32, ServerFnError> {
|
||||
Ok(COUNT.load(Ordering::Relaxed))
|
||||
}
|
||||
|
||||
#[server(AdjustServerCount, "/api")]
|
||||
#[server]
|
||||
pub async fn adjust_server_count(
|
||||
delta: i32,
|
||||
msg: String,
|
||||
|
@ -33,7 +32,7 @@ pub async fn adjust_server_count(
|
|||
Ok(new)
|
||||
}
|
||||
|
||||
#[server(ClearServerCount, "/api")]
|
||||
#[server]
|
||||
pub async fn clear_server_count() -> Result<i32, ServerFnError> {
|
||||
COUNT.store(0, Ordering::Relaxed);
|
||||
_ = COUNT_CHANNEL.send(&0).await;
|
||||
|
@ -147,6 +146,8 @@ pub fn Counter() -> impl IntoView {
|
|||
// but uses HTML forms to submit the actions
|
||||
#[component]
|
||||
pub fn FormCounter() -> impl IntoView {
|
||||
// these struct names are auto-generated by #[server]
|
||||
// they are just the PascalCased versions of the function names
|
||||
let adjust = create_server_action::<AdjustServerCount>();
|
||||
let clear = create_server_action::<ClearServerCount>();
|
||||
|
||||
|
|
|
@ -107,7 +107,8 @@ pub async fn add_todo(title: String) -> Result<(), ServerFnError> {
|
|||
}
|
||||
}
|
||||
|
||||
#[server(DeleteTodo, "/api")]
|
||||
// The struct name and path prefix arguments are optional.
|
||||
#[server]
|
||||
pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> {
|
||||
let pool = pool()?;
|
||||
|
||||
|
|
|
@ -173,7 +173,7 @@ pub struct PostMetadata {
|
|||
title: String,
|
||||
}
|
||||
|
||||
#[server(ListPostMetadata, "/api")]
|
||||
#[server]
|
||||
pub async fn list_post_metadata() -> Result<Vec<PostMetadata>, ServerFnError> {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
Ok(POSTS
|
||||
|
@ -185,7 +185,7 @@ pub async fn list_post_metadata() -> Result<Vec<PostMetadata>, ServerFnError> {
|
|||
.collect())
|
||||
}
|
||||
|
||||
#[server(GetPost, "/api")]
|
||||
#[server]
|
||||
pub async fn get_post(id: usize) -> Result<Option<Post>, ServerFnError> {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
Ok(POSTS.iter().find(|post| post.id == id).cloned())
|
||||
|
|
|
@ -173,7 +173,7 @@ pub struct PostMetadata {
|
|||
title: String,
|
||||
}
|
||||
|
||||
#[server(ListPostMetadata, "/api")]
|
||||
#[server]
|
||||
pub async fn list_post_metadata() -> Result<Vec<PostMetadata>, ServerFnError> {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
Ok(POSTS
|
||||
|
@ -185,7 +185,7 @@ pub async fn list_post_metadata() -> Result<Vec<PostMetadata>, ServerFnError> {
|
|||
.collect())
|
||||
}
|
||||
|
||||
#[server(GetPost, "/api")]
|
||||
#[server]
|
||||
pub async fn get_post(id: usize) -> Result<Option<Post>, ServerFnError> {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
Ok(POSTS.iter().find(|post| post.id == id).cloned())
|
||||
|
|
|
@ -4,14 +4,14 @@ use leptos_router::*;
|
|||
const WAIT_ONE_SECOND: u64 = 1;
|
||||
const WAIT_TWO_SECONDS: u64 = 2;
|
||||
|
||||
#[server(FirstWaitFn "/api")]
|
||||
#[server]
|
||||
async fn first_wait_fn(seconds: u64) -> Result<(), ServerFnError> {
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(seconds)).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[server(SecondWaitFn "/api")]
|
||||
#[server]
|
||||
async fn second_wait_fn(seconds: u64) -> Result<(), ServerFnError> {
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(seconds)).await;
|
||||
|
||||
|
|
|
@ -62,7 +62,8 @@ pub async fn add_todo(title: String) -> Result<(), ServerFnError> {
|
|||
}
|
||||
}
|
||||
|
||||
#[server(DeleteTodo, "/api")]
|
||||
// The struct name and path prefix arguments are optional.
|
||||
#[server]
|
||||
pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> {
|
||||
let mut conn = db().await?;
|
||||
|
||||
|
|
|
@ -79,7 +79,8 @@ pub async fn add_todo(title: String) -> Result<(), ServerFnError> {
|
|||
}
|
||||
}
|
||||
|
||||
#[server(DeleteTodo, "/api")]
|
||||
// The struct name and path prefix arguments are optional.
|
||||
#[server]
|
||||
pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> {
|
||||
let mut conn = db().await?;
|
||||
|
||||
|
|
|
@ -79,7 +79,8 @@ pub async fn add_todo(title: String) -> Result<(), ServerFnError> {
|
|||
}
|
||||
}
|
||||
|
||||
#[server(DeleteTodo, "/api")]
|
||||
// The struct name and path prefix arguments are optional.
|
||||
#[server]
|
||||
pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> {
|
||||
let mut conn = db().await?;
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ use proc_macro::TokenStream;
|
|||
use proc_macro2::{Span, TokenTree};
|
||||
use quote::ToTokens;
|
||||
use rstml::{node::KeyedAttribute, parse};
|
||||
use server_fn_macro::server_macro_impl;
|
||||
use syn::parse_macro_input;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
|
@ -34,6 +33,7 @@ mod params;
|
|||
mod view;
|
||||
use view::{client_template::render_template, render_view};
|
||||
mod component;
|
||||
mod server;
|
||||
mod slot;
|
||||
|
||||
/// The `view` macro uses RSX (like JSX, but Rust!) It follows most of the
|
||||
|
@ -760,17 +760,23 @@ pub fn slot(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
|||
/// If you call a server function from the client (i.e., when the `csr` or `hydrate` features
|
||||
/// are enabled), it will instead make a network request to the server.
|
||||
///
|
||||
/// You can specify one, two, three, or four arguments to the server function:
|
||||
/// 1. **Required**: A type name that will be used to identify and register the server function
|
||||
/// (e.g., `MyServerFn`).
|
||||
/// 2. *Optional*: A URL prefix at which the function will be mounted when it’s registered
|
||||
/// (e.g., `"/api"`). Defaults to `"/"`.
|
||||
/// 3. *Optional*: The encoding for the server function (`"Url"`, `"Cbor"`, `"GetJson"`, or `"GetCbor`". See **Server Function Encodings** below.)
|
||||
/// 4. *Optional*: A specific endpoint path to be used in the URL. (By default, a unique path will be generated.)
|
||||
/// You can specify one, two, three, or four arguments to the server function. All of these arguments are optional.
|
||||
/// 1. A type name that will be used to identify and register the server function
|
||||
/// (e.g., `MyServerFn`). Defaults to a PascalCased version of the function name.
|
||||
/// 2. A URL prefix at which the function will be mounted when it’s registered
|
||||
/// (e.g., `"/api"`). Defaults to `"/api"`.
|
||||
/// 3. The encoding for the server function (`"Url"`, `"Cbor"`, `"GetJson"`, or `"GetCbor`". See **Server Function Encodings** below.)
|
||||
/// 4. A specific endpoint path to be used in the URL. (By default, a unique path will be generated.)
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// // will generate a server function at `/api-prefix/hello`
|
||||
/// #[server(MyServerFnType, "/api-prefix", "Url", "hello")]
|
||||
/// pub async fn my_server_fn_type() /* ... */
|
||||
///
|
||||
/// // will generate a server function with struct `HelloWorld` and path
|
||||
/// // `/api/hello2349232342342` (hash based on location in source)
|
||||
/// #[server]
|
||||
/// pub async fn hello_world() /* ... */
|
||||
/// ```
|
||||
///
|
||||
/// The server function itself can take any number of arguments, each of which should be serializable
|
||||
|
@ -859,16 +865,7 @@ pub fn slot(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
|||
#[proc_macro_attribute]
|
||||
#[proc_macro_error]
|
||||
pub fn server(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||
match server_macro_impl(
|
||||
args.into(),
|
||||
s.into(),
|
||||
syn::parse_quote!(::leptos::leptos_server::ServerFnTraitObj),
|
||||
None,
|
||||
Some(syn::parse_quote!(::leptos::server_fn)),
|
||||
) {
|
||||
Err(e) => e.to_compile_error().into(),
|
||||
Ok(s) => s.to_token_stream().into(),
|
||||
}
|
||||
server::server_impl(args, s)
|
||||
}
|
||||
|
||||
/// Derives a trait that parses a map of string keys and values into a typed
|
||||
|
|
109
leptos_macro/src/server.rs
Normal file
109
leptos_macro/src/server.rs
Normal file
|
@ -0,0 +1,109 @@
|
|||
use convert_case::{Case, Converter};
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::Literal;
|
||||
use quote::{ToTokens, __private::TokenStream as TokenStream2};
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
Ident, ItemFn, Token,
|
||||
};
|
||||
|
||||
pub fn server_impl(
|
||||
args: proc_macro::TokenStream,
|
||||
s: TokenStream,
|
||||
) -> TokenStream {
|
||||
let function: syn::ItemFn =
|
||||
match syn::parse(s).map_err(|e| e.to_compile_error()) {
|
||||
Ok(f) => f,
|
||||
Err(e) => return e.into(),
|
||||
};
|
||||
let ItemFn {
|
||||
attrs,
|
||||
vis,
|
||||
sig,
|
||||
block,
|
||||
} = function;
|
||||
// TODO apply middleware: https://github.com/leptos-rs/leptos/issues/1461
|
||||
let mapped_body = quote::quote! {
|
||||
#(#attrs)*
|
||||
#vis #sig {
|
||||
#block
|
||||
}
|
||||
};
|
||||
|
||||
let mut args: ServerFnArgs = match syn::parse(args) {
|
||||
Ok(args) => args,
|
||||
Err(e) => return e.to_compile_error().into(),
|
||||
};
|
||||
// default to PascalCase version of function name if no struct name given
|
||||
if args.struct_name.is_none() {
|
||||
let upper_cammel_case_name = Converter::new()
|
||||
.from_case(Case::Snake)
|
||||
.to_case(Case::UpperCamel)
|
||||
.convert(sig.ident.to_string());
|
||||
args.struct_name =
|
||||
Some(Ident::new(&upper_cammel_case_name, sig.ident.span()));
|
||||
}
|
||||
// default to "/api" if no prefix given
|
||||
if args.prefix.is_none() {
|
||||
args.prefix = Some(Literal::string("/api"));
|
||||
}
|
||||
|
||||
match server_fn_macro::server_macro_impl(
|
||||
quote::quote!(#args),
|
||||
mapped_body,
|
||||
syn::parse_quote!(::leptos::leptos_server::ServerFnTraitObj),
|
||||
None,
|
||||
Some(syn::parse_quote!(::leptos::server_fn)),
|
||||
) {
|
||||
Err(e) => e.to_compile_error().into(),
|
||||
Ok(s) => s.to_token_stream().into(),
|
||||
}
|
||||
}
|
||||
|
||||
struct ServerFnArgs {
|
||||
struct_name: Option<Ident>,
|
||||
_comma: Option<Token![,]>,
|
||||
prefix: Option<Literal>,
|
||||
_comma2: Option<Token![,]>,
|
||||
encoding: Option<Literal>,
|
||||
_comma3: Option<Token![,]>,
|
||||
fn_path: Option<Literal>,
|
||||
}
|
||||
|
||||
impl ToTokens for ServerFnArgs {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let struct_name =
|
||||
self.struct_name.as_ref().map(|s| quote::quote! { #s, });
|
||||
let prefix = self.prefix.as_ref().map(|p| quote::quote! { #p, });
|
||||
let encoding = self.encoding.as_ref().map(|e| quote::quote! { #e, });
|
||||
let fn_path = self.fn_path.as_ref().map(|f| quote::quote! { #f, });
|
||||
tokens.extend(quote::quote! {
|
||||
#struct_name
|
||||
#prefix
|
||||
#encoding
|
||||
#fn_path
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for ServerFnArgs {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let struct_name = input.parse()?;
|
||||
let _comma = input.parse()?;
|
||||
let prefix = input.parse()?;
|
||||
let _comma2 = input.parse()?;
|
||||
let encoding = input.parse()?;
|
||||
let _comma3 = input.parse()?;
|
||||
let fn_path = input.parse()?;
|
||||
|
||||
Ok(Self {
|
||||
struct_name,
|
||||
_comma,
|
||||
prefix,
|
||||
_comma2,
|
||||
encoding,
|
||||
_comma3,
|
||||
fn_path,
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue