allow type paths for input/output, and properly namespace built-in encodings

This commit is contained in:
Greg Johnston 2024-01-10 08:03:37 -05:00
parent 738eeefe73
commit 61148026d1
3 changed files with 77 additions and 36 deletions

View File

@ -20,7 +20,6 @@ leptos = { path = "../../leptos", features = ["nightly"] }
leptos_actix = { path = "../../integrations/actix", optional = true }
leptos_meta = { path = "../../meta", features = ["nightly"] }
leptos_router = { path = "../../router", features = ["nightly"] }
server_fn = { path = "../../server_fn", features = ["cbor"] }
log = "0.4.17"
simple_logger = "4.0.0"
gloo = { git = "https://github.com/rustwasm/gloo" }

View File

@ -22,8 +22,9 @@ cfg_if! {
}
}
/// Server functions can be given doc comments.
#[server(GetTodos, "/api")]
/// This is an example of a server function using an alternative CBOR encoding. Both the function arguments being sent
/// to the server and the server response will be encoded with CBOR. Good for binary data that doesn't encode well via the default methods
#[server(encoding = "Cbor")]
pub async fn get_todos() -> Result<Vec<Todo>, ServerFnError> {
// this is just an example of how to access server context injected in the handlers
let req = use_context::<actix_web::HttpRequest>();
@ -44,9 +45,8 @@ pub async fn get_todos() -> Result<Vec<Todo>, ServerFnError> {
Ok(todos)
}
// This is an example of leptos's server functions using an alternative CBOR encoding. Both the function arguments being sent
// to the server and the server response will be encoded with CBOR. Good for binary data that doesn't encode well via the default methods
#[server(AddTodo, "/api", "Cbor")]
#[server]
pub async fn add_todo(title: String) -> Result<(), ServerFnError> {
let mut conn = db().await?;

View File

@ -13,7 +13,7 @@ use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
spanned::Spanned,
*,
Type, *,
};
/// The implementation of the `server_fn` macro.
@ -71,23 +71,43 @@ pub fn server_macro_impl(
input,
output,
fn_path,
builtin_encoding,
} = args;
let prefix = prefix.unwrap_or_else(|| Literal::string(default_path));
let fn_path = fn_path.unwrap_or_else(|| Literal::string(""));
let input_ident = input
.as_ref()
.map(ToString::to_string)
.unwrap_or_else(|| "PostUrl".to_string());
let input = input.map(|n| n.to_token_stream()).unwrap_or_else(|| {
quote! {
#server_fn_path::codec::PostUrl
let input_ident = match &input {
Some(Type::Path(path)) => {
path.path.segments.last().map(|seg| seg.ident.to_string())
}
});
let output = output.map(|n| n.to_token_stream()).unwrap_or_else(|| {
quote! {
#server_fn_path::codec::Json
}
});
None => Some("PostUrl".to_string()),
_ => None,
};
let input = input
.map(|n| {
if builtin_encoding {
quote! { #server_fn_path::codec::#n }
} else {
n.to_token_stream()
}
})
.unwrap_or_else(|| {
quote! {
#server_fn_path::codec::PostUrl
}
});
let output = output
.map(|n| {
if builtin_encoding {
quote! { #server_fn_path::codec::#n }
} else {
n.to_token_stream()
}
})
.unwrap_or_else(|| {
quote! {
#server_fn_path::codec::Json
}
});
// default to PascalCase version of function name if no struct name given
let struct_name = struct_name.unwrap_or_else(|| {
let upper_camel_case_name = Converter::new()
@ -316,10 +336,10 @@ pub fn server_macro_impl(
}
};
let (is_serde, derives) = match input_ident.as_str() {
"Rkyv" => todo!("implement derives for Rkyv"),
"MultipartFormData" => (false, quote! {}),
"SerdeLite" => (
let (is_serde, derives) = match input_ident.as_deref() {
Some("Rkyv") => todo!("implement derives for Rkyv"),
Some("MultipartFormData") => (false, quote! {}),
Some("SerdeLite") => (
true,
quote! {
Clone, #server_fn_path::serde_lite::Serialize, #server_fn_path::serde_lite::Deserialize
@ -470,6 +490,21 @@ pub fn server_macro_impl(
})
}
fn type_from_ident(ident: Ident) -> Type {
let mut segments = Punctuated::new();
segments.push(PathSegment {
ident,
arguments: PathArguments::None,
});
Type::Path(TypePath {
qself: None,
path: Path {
leading_colon: None,
segments,
},
})
}
#[derive(Debug)]
struct Middleware {
expr: syn::Expr,
@ -555,9 +590,10 @@ fn err_type(return_ty: &Type) -> Result<Option<&GenericArgument>> {
struct ServerFnArgs {
struct_name: Option<Ident>,
prefix: Option<Literal>,
input: Option<Ident>,
output: Option<Ident>,
input: Option<Type>,
output: Option<Type>,
fn_path: Option<Literal>,
builtin_encoding: bool,
}
impl Parse for ServerFnArgs {
@ -569,8 +605,8 @@ impl Parse for ServerFnArgs {
let mut fn_path: Option<Literal> = None;
// new arguments: can only be keyed by name
let mut input: Option<Ident> = None;
let mut output: Option<Ident> = None;
let mut input: Option<Type> = None;
let mut output: Option<Type> = None;
let mut use_key_and_value = false;
let mut arg_pos = 0;
@ -698,23 +734,28 @@ impl Parse for ServerFnArgs {
}
// parse legacy encoding into input/output
let mut builtin_encoding = false;
if let Some(encoding) = encoding {
match encoding.to_string().to_lowercase().as_str() {
"\"url\"" => {
input = syn::parse_quote!(PostUrl);
output = syn::parse_quote!(Json);
input = Some(type_from_ident(syn::parse_quote!(PostUrl)));
output = Some(type_from_ident(syn::parse_quote!(Json)));
builtin_encoding = true;
}
"\"cbor\"" => {
input = syn::parse_quote!(Cbor);
output = syn::parse_quote!(Cbor);
input = Some(type_from_ident(syn::parse_quote!(Cbor)));
output = Some(type_from_ident(syn::parse_quote!(Cbor)));
builtin_encoding = true;
}
"\"getcbor\"" => {
input = syn::parse_quote!(GetUrl);
output = syn::parse_quote!(Cbor);
input = Some(type_from_ident(syn::parse_quote!(GetUrl)));
output = Some(type_from_ident(syn::parse_quote!(Cbor)));
builtin_encoding = true;
}
"\"getjson\"" => {
input = syn::parse_quote!(GetUrl);
output = syn::parse_quote!(Json);
input = Some(type_from_ident(syn::parse_quote!(GetUrl)));
output = Some(type_from_ident(syn::parse_quote!(Json)));
builtin_encoding = true;
}
_ => {
return Err(syn::Error::new(
@ -732,6 +773,7 @@ impl Parse for ServerFnArgs {
input,
output,
fn_path,
builtin_encoding,
})
}
}