allow custom req/res/client types

This commit is contained in:
Greg Johnston 2024-01-19 15:48:14 -05:00
parent 1b1e02729e
commit 46e7abf9ba
4 changed files with 84 additions and 5 deletions

View file

@ -871,12 +871,15 @@ pub fn slot(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
/// relative to the prefix (defaults to the function name followed by unique hash)
/// - `input`: the encoding for the arguments (defaults to `PostUrl`)
/// - `output`: the encoding for the response (defaults to `Json`)
/// - `client`: a custom `Client` implementation that will be used for this server fn
/// - `encoding`: (legacy, may be deprecated in future) specifies the encoding, which may be one
/// of the following (not case sensitive)
/// - `"Url"`: `POST` request with URL-encoded arguments and JSON response
/// - `"GetUrl"`: `GET` request with URL-encoded arguments and JSON response
/// - `"Cbor"`: `POST` request with CBOR-encoded arguments and response
/// - `"GetCbor"`: `GET` request with URL-encoded arguments and CBOR response
/// - `req` and `res` specify the HTTP request and response types to be used on the server (these
/// should usually only be necessary if you are integrating with a server other than Actix/Axum)
///
/// ```rust,ignore
/// #[server(
@ -949,6 +952,8 @@ pub fn server(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
s.into(),
Some(syn::parse_quote!(::leptos::server_fn)),
"/api",
None,
None,
) {
Err(e) => e.to_compile_error().into(),
Ok(s) => s.to_token_stream().into(),

View file

@ -34,12 +34,15 @@ use syn::__private::ToTokens;
/// relative to the prefix (defaults to the function name followed by unique hash)
/// - `input`: the encoding for the arguments (defaults to `PostUrl`)
/// - `output`: the encoding for the response (defaults to `Json`)
/// - `client`: a custom `Client` implementation that will be used for this server fn
/// - `encoding`: (legacy, may be deprecated in future) specifies the encoding, which may be one
/// of the following (not case sensitive)
/// - `"Url"`: `POST` request with URL-encoded arguments and JSON response
/// - `"GetUrl"`: `GET` request with URL-encoded arguments and JSON response
/// - `"Cbor"`: `POST` request with CBOR-encoded arguments and response
/// - `"GetCbor"`: `GET` request with URL-encoded arguments and CBOR response
/// - `req` and `res` specify the HTTP request and response types to be used on the server (these
/// should usually only be necessary if you are integrating with a server other than Actix/Axum)
/// ```rust,ignore
/// #[server(
/// name = SomeStructName,
@ -71,6 +74,8 @@ pub fn server(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
s.into(),
Some(syn::parse_quote!(server_fns)),
"/api",
None,
None,
) {
Err(e) => e.to_compile_error().into(),
Ok(s) => s.to_token_stream().into(),

View file

@ -20,3 +20,4 @@ nightly = []
ssr = []
actix = []
axum = []
reqwest = []

View file

@ -35,6 +35,8 @@ pub fn server_macro_impl(
body: TokenStream2,
server_fn_path: Option<Path>,
default_path: &str,
preset_req: Option<Type>,
preset_res: Option<Type>,
) -> Result<TokenStream2> {
let mut body = syn::parse::<ServerFnBody>(body.into())?;
@ -65,7 +67,12 @@ pub fn server_macro_impl(
output,
fn_path,
builtin_encoding,
req_ty,
res_ty,
client,
custom_wrapper,
} = args;
_ = custom_wrapper; // TODO: this should be used to enable custom encodings
let prefix = prefix.unwrap_or_else(|| Literal::string(default_path));
let fn_path = fn_path.unwrap_or_else(|| Literal::string(""));
let input_ident = match &input {
@ -380,9 +387,16 @@ pub fn server_macro_impl(
PathInfo::None => quote! {},
};
// TODO reqwest
let client = quote! {
#server_fn_path::client::browser::BrowserClient
let client = if let Some(client) = client {
client.to_token_stream()
} else if cfg!(feature = "reqwest") {
quote! {
#server_fn_path::client::reqwest::ReqwestClient
}
} else {
quote! {
#server_fn_path::client::browser::BrowserClient
}
};
let req = if !cfg!(feature = "ssr") {
@ -397,11 +411,16 @@ pub fn server_macro_impl(
quote! {
#server_fn_path::request::actix::ActixRequest
}
} else if let Some(req_ty) = req_ty {
req_ty.to_token_stream()
} else if let Some(req_ty) = preset_req {
req_ty.to_token_stream()
} else {
return Err(syn::Error::new(
Span::call_site(),
"If the `ssr` feature is enabled, either the `actix` or `axum` \
features should also be enabled.",
features should also be enabled, or the `req = ` argument should \
be provided to specify the request type.",
));
};
let res = if !cfg!(feature = "ssr") {
@ -416,11 +435,16 @@ pub fn server_macro_impl(
quote! {
#server_fn_path::response::actix::ActixResponse
}
} else if let Some(res_ty) = res_ty {
res_ty.to_token_stream()
} else if let Some(res_ty) = preset_res {
res_ty.to_token_stream()
} else {
return Err(syn::Error::new(
Span::call_site(),
"If the `ssr` feature is enabled, either the `actix` or `axum` \
features should also be enabled.",
features should also be enabled, or the `res = ` argument should \
be provided to specify the response type.",
));
};
@ -614,6 +638,10 @@ struct ServerFnArgs {
input: Option<Type>,
output: Option<Type>,
fn_path: Option<Literal>,
req_ty: Option<Type>,
res_ty: Option<Type>,
client: Option<Type>,
custom_wrapper: Option<Type>,
builtin_encoding: bool,
}
@ -628,6 +656,10 @@ impl Parse for ServerFnArgs {
// new arguments: can only be keyed by name
let mut input: Option<Type> = None;
let mut output: Option<Type> = None;
let mut req_ty: Option<Type> = None;
let mut res_ty: Option<Type> = None;
let mut client: Option<Type> = None;
let mut custom_wrapper: Option<Type> = None;
let mut use_key_and_value = false;
let mut arg_pos = 0;
@ -703,6 +735,38 @@ impl Parse for ServerFnArgs {
));
}
output = Some(stream.parse()?);
} else if key == "req" {
if req_ty.is_some() {
return Err(syn::Error::new(
key.span(),
"keyword argument repeated: `req`",
));
}
req_ty = Some(stream.parse()?);
} else if key == "res" {
if res_ty.is_some() {
return Err(syn::Error::new(
key.span(),
"keyword argument repeated: `res`",
));
}
res_ty = Some(stream.parse()?);
} else if key == "client" {
if client.is_some() {
return Err(syn::Error::new(
key.span(),
"keyword argument repeated: `client`",
));
}
client = Some(stream.parse()?);
} else if key == "custom" {
if custom_wrapper.is_some() {
return Err(syn::Error::new(
key.span(),
"keyword argument repeated: `custom`",
));
}
custom_wrapper = Some(stream.parse()?);
} else {
return Err(lookahead.error());
}
@ -794,6 +858,10 @@ impl Parse for ServerFnArgs {
output,
fn_path,
builtin_encoding,
req_ty,
res_ty,
client,
custom_wrapper,
})
}
}