Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: make struct name and path optional for server functions #1573

Merged
merged 2 commits into from
Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions examples/counter_isomorphic/src/counters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -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>();

Expand Down
3 changes: 2 additions & 1 deletion examples/session_auth_axum/src/todo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()?;

Expand Down
4 changes: 2 additions & 2 deletions examples/ssr_modes/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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())
Expand Down
4 changes: 2 additions & 2 deletions examples/ssr_modes_axum/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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())
Expand Down
4 changes: 2 additions & 2 deletions examples/suspense_tests/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
3 changes: 2 additions & 1 deletion examples/todo_app_sqlite/src/todo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?;

Expand Down
3 changes: 2 additions & 1 deletion examples/todo_app_sqlite_axum/src/todo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?;

Expand Down
3 changes: 2 additions & 1 deletion examples/todo_app_sqlite_viz/src/todo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?;

Expand Down
33 changes: 15 additions & 18 deletions leptos_macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
109 changes: 109 additions & 0 deletions leptos_macro/src/server.rs
Original file line number Diff line number Diff line change
@@ -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,
})
}
}