mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-28 18:22:34 -05:00
Compare commits
7 Commits
gbj-patch-
...
0.6.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9de34b74cf | ||
|
|
1b5961edaa | ||
|
|
26d1aee9ad | ||
|
|
2bf09384df | ||
|
|
ac12e1a411 | ||
|
|
b367b68a43 | ||
|
|
1f9dad421f |
26
Cargo.toml
26
Cargo.toml
@@ -25,22 +25,22 @@ members = [
|
||||
exclude = ["benchmarks", "examples"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.6.0-rc1"
|
||||
version = "0.6.2"
|
||||
|
||||
[workspace.dependencies]
|
||||
leptos = { path = "./leptos", version = "0.6.0-rc1" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.6.0-rc1" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.6.0-rc1" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.6.0-rc1" }
|
||||
leptos_reactive = { path = "./leptos_reactive", version = "0.6.0-rc1" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.6.0-rc1" }
|
||||
server_fn = { path = "./server_fn", version = "0.6.0-rc1" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.6.0-rc1" }
|
||||
leptos = { path = "./leptos", version = "0.6.2" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.6.2" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.6.2" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.6.2" }
|
||||
leptos_reactive = { path = "./leptos_reactive", version = "0.6.2" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.6.2" }
|
||||
server_fn = { path = "./server_fn", version = "0.6.2" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.6.2" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.6" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.6.0-rc1" }
|
||||
leptos_router = { path = "./router", version = "0.6.0-rc1" }
|
||||
leptos_meta = { path = "./meta", version = "0.6.0-rc1" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.6.0-rc1" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.6.2" }
|
||||
leptos_router = { path = "./router", version = "0.6.2" }
|
||||
leptos_meta = { path = "./meta", version = "0.6.2" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.6.2" }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
|
||||
@@ -1390,15 +1390,13 @@ impl LeptosRoutes for &mut ServiceConfig {
|
||||
/// Ok(data)
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn extract<T, CustErr>() -> Result<T, ServerFnError<CustErr>>
|
||||
pub async fn extract<T>() -> Result<T, ServerFnError>
|
||||
where
|
||||
T: actix_web::FromRequest,
|
||||
<T as FromRequest>::Error: Display,
|
||||
{
|
||||
let req = use_context::<HttpRequest>().ok_or_else(|| {
|
||||
ServerFnError::ServerError(
|
||||
"HttpRequest should have been provided via context".to_string(),
|
||||
)
|
||||
ServerFnError::new("HttpRequest should have been provided via context")
|
||||
})?;
|
||||
|
||||
T::extract(&req)
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//! To run in this environment, you need to disable the default feature set and enable
|
||||
//! the `wasm` feature on `leptos_axum` in your `Cargo.toml`.
|
||||
//! ```toml
|
||||
//! leptos_axum = { version = "0.6.0-rc1", default-features = false, features = ["wasm"] }
|
||||
//! leptos_axum = { version = "0.6.0", default-features = false, features = ["wasm"] }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Features
|
||||
@@ -55,10 +55,7 @@ use leptos_router::*;
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::RwLock;
|
||||
use server_fn::redirect::REDIRECT_HEADER;
|
||||
use std::{
|
||||
error::Error, fmt::Debug, io, pin::Pin, sync::Arc,
|
||||
thread::available_parallelism,
|
||||
};
|
||||
use std::{fmt::Debug, io, pin::Pin, sync::Arc, thread::available_parallelism};
|
||||
use tokio_util::task::LocalPoolHandle;
|
||||
use tracing::Instrument;
|
||||
|
||||
@@ -1772,13 +1769,12 @@ fn get_leptos_pool() -> LocalPoolHandle {
|
||||
/// Ok(query)
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn extract<T, CustErr>() -> Result<T, ServerFnError>
|
||||
pub async fn extract<T>() -> Result<T, ServerFnError>
|
||||
where
|
||||
T: Sized + FromRequestParts<()>,
|
||||
T::Rejection: Debug,
|
||||
CustErr: Error + 'static,
|
||||
{
|
||||
extract_with_state::<T, (), CustErr>(&()).await
|
||||
extract_with_state::<T, ()>(&()).await
|
||||
}
|
||||
|
||||
/// A helper to make it easier to use Axum extractors in server functions. This
|
||||
@@ -1800,18 +1796,14 @@ where
|
||||
/// Ok(query)
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn extract_with_state<T, S, CustErr>(
|
||||
state: &S,
|
||||
) -> Result<T, ServerFnError>
|
||||
pub async fn extract_with_state<T, S>(state: &S) -> Result<T, ServerFnError>
|
||||
where
|
||||
T: Sized + FromRequestParts<S>,
|
||||
T::Rejection: Debug,
|
||||
CustErr: Error + 'static,
|
||||
{
|
||||
let mut parts = use_context::<Parts>().ok_or_else(|| {
|
||||
ServerFnError::ServerError::<CustErr>(
|
||||
"should have had Parts provided by the leptos_axum integration"
|
||||
.to_string(),
|
||||
ServerFnError::new(
|
||||
"should have had Parts provided by the leptos_axum integration",
|
||||
)
|
||||
})?;
|
||||
T::from_request_parts(&mut parts, state)
|
||||
|
||||
@@ -915,6 +915,16 @@ pub fn slot(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||
/// Whatever encoding is provided to `input` should implement `IntoReq` and `FromReq`. Whatever encoding is provided
|
||||
/// to `output` should implement `IntoRes` and `FromRes`.
|
||||
///
|
||||
/// ## Default Values for Parameters
|
||||
///
|
||||
/// Individual function parameters can be annotated with `#[server(default)]`, which will pass
|
||||
/// through `#[serde(default)]`. This is useful for the empty values of arguments with some
|
||||
/// encodings. The URL encoding, for example, omits a field entirely if it is an empty `Vec<_>`,
|
||||
/// but this causes a deserialization error: the correct solution is to add `#[server(default)]`.
|
||||
/// ```rust,ignore
|
||||
/// pub async fn with_default_value(#[server(default)] values: Vec<u32>) /* etc. */
|
||||
/// ```
|
||||
///
|
||||
/// ## Important Notes
|
||||
/// - **Server functions must be `async`.** Even if the work being done inside the function body
|
||||
/// can run synchronously on the server, from the client’s perspective it involves an asynchronous
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_meta"
|
||||
version = "0.6.0-rc1"
|
||||
version = "0.6.2"
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_router"
|
||||
version = "0.6.0-rc1"
|
||||
version = "0.6.2"
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
name = "server_fn"
|
||||
version = { workspace = true }
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
authors = ["Greg Johnston", "Ben Wishovich"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/leptos-rs/leptos"
|
||||
description = "RPC for any web framework."
|
||||
readme = "../README.md"
|
||||
|
||||
[dependencies]
|
||||
server_fn_macro_default = "0.6.0-rc1"
|
||||
server_fn_macro_default = { workspace = true }
|
||||
# used for hashing paths in #[server] macro
|
||||
const_format = "0.2"
|
||||
xxhash-rust = { version = "0.8", features = ["const_xxh64"] }
|
||||
@@ -18,7 +18,7 @@ serde = { version = "1", features = ["derive"] }
|
||||
send_wrapper = { version = "0.6", features = ["futures"], optional = true }
|
||||
|
||||
# registration system
|
||||
inventory = {version="0.3",optional=true}
|
||||
inventory = { version = "0.3", optional = true }
|
||||
dashmap = "5"
|
||||
once_cell = "1"
|
||||
|
||||
@@ -72,11 +72,11 @@ reqwest = { version = "0.11", default-features = false, optional = true, feature
|
||||
url = "2"
|
||||
|
||||
[features]
|
||||
default = [ "json", "cbor"]
|
||||
default = ["json", "cbor"]
|
||||
form-redirects = []
|
||||
actix = ["ssr", "dep:actix-web", "dep:send_wrapper"]
|
||||
axum = [
|
||||
"ssr",
|
||||
"ssr",
|
||||
"dep:axum",
|
||||
"dep:hyper",
|
||||
"dep:http-body-util",
|
||||
@@ -109,4 +109,21 @@ all-features = true
|
||||
# disables some feature combos for testing in CI
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["rustls", "default-tls", "form-redirects"]
|
||||
skip_feature_sets = [["actix", "axum"], ["browser", "actix"], ["browser", "axum"], ["browser", "reqwest"]]
|
||||
skip_feature_sets = [
|
||||
[
|
||||
"actix",
|
||||
"axum",
|
||||
],
|
||||
[
|
||||
"browser",
|
||||
"actix",
|
||||
],
|
||||
[
|
||||
"browser",
|
||||
"axum",
|
||||
],
|
||||
[
|
||||
"browser",
|
||||
"reqwest",
|
||||
],
|
||||
]
|
||||
|
||||
62
server_fn/src/request/spin.rs
Normal file
62
server_fn/src/request/spin.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use crate::{error::ServerFnError, request::Req};
|
||||
use axum::body::{Body, Bytes};
|
||||
use futures::{Stream, StreamExt};
|
||||
use http::{
|
||||
header::{ACCEPT, CONTENT_TYPE, REFERER},
|
||||
Request,
|
||||
};
|
||||
use http_body_util::BodyExt;
|
||||
use std::borrow::Cow;
|
||||
|
||||
impl<CustErr> Req<CustErr> for IncomingRequest
|
||||
where
|
||||
CustErr: 'static,
|
||||
{
|
||||
fn as_query(&self) -> Option<&str> {
|
||||
self.uri().query()
|
||||
}
|
||||
|
||||
fn to_content_type(&self) -> Option<Cow<'_, str>> {
|
||||
self.headers()
|
||||
.get(CONTENT_TYPE)
|
||||
.map(|h| String::from_utf8_lossy(h.as_bytes()))
|
||||
}
|
||||
|
||||
fn accepts(&self) -> Option<Cow<'_, str>> {
|
||||
self.headers()
|
||||
.get(ACCEPT)
|
||||
.map(|h| String::from_utf8_lossy(h.as_bytes()))
|
||||
}
|
||||
|
||||
fn referer(&self) -> Option<Cow<'_, str>> {
|
||||
self.headers()
|
||||
.get(REFERER)
|
||||
.map(|h| String::from_utf8_lossy(h.as_bytes()))
|
||||
}
|
||||
|
||||
async fn try_into_bytes(self) -> Result<Bytes, ServerFnError<CustErr>> {
|
||||
let (_parts, body) = self.into_parts();
|
||||
|
||||
body.collect()
|
||||
.await
|
||||
.map(|c| c.to_bytes())
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
}
|
||||
|
||||
async fn try_into_string(self) -> Result<String, ServerFnError<CustErr>> {
|
||||
let bytes = self.try_into_bytes().await?;
|
||||
String::from_utf8(bytes.to_vec())
|
||||
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
}
|
||||
|
||||
fn try_into_stream(
|
||||
self,
|
||||
) -> Result<
|
||||
impl Stream<Item = Result<Bytes, ServerFnError>> + Send + 'static,
|
||||
ServerFnError<CustErr>,
|
||||
> {
|
||||
Ok(self.into_body().into_data_stream().map(|chunk| {
|
||||
chunk.map_err(|e| ServerFnError::Deserialization(e.to_string()))
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,54 @@ pub fn server_macro_impl(
|
||||
}
|
||||
});
|
||||
|
||||
let fields = body
|
||||
.inputs
|
||||
.iter_mut()
|
||||
.map(|f| {
|
||||
let typed_arg = match f {
|
||||
FnArg::Receiver(_) => {
|
||||
return Err(syn::Error::new(
|
||||
f.span(),
|
||||
"cannot use receiver types in server function macro",
|
||||
))
|
||||
}
|
||||
FnArg::Typed(t) => t,
|
||||
};
|
||||
|
||||
// strip `mut`, which is allowed in fn args but not in struct fields
|
||||
if let Pat::Ident(ident) = &mut *typed_arg.pat {
|
||||
ident.mutability = None;
|
||||
}
|
||||
|
||||
// allow #[server(default)] on fields
|
||||
let mut default = false;
|
||||
let mut other_attrs = Vec::new();
|
||||
for attr in typed_arg.attrs.iter() {
|
||||
if !attr.path().is_ident("server") {
|
||||
other_attrs.push(attr.clone());
|
||||
continue;
|
||||
}
|
||||
attr.parse_nested_meta(|meta| {
|
||||
if meta.path.is_ident("default") && meta.input.is_empty() {
|
||||
default = true;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(meta.error(
|
||||
"Unrecognized #[server] attribute, expected \
|
||||
#[server(default)]",
|
||||
))
|
||||
}
|
||||
})?;
|
||||
}
|
||||
typed_arg.attrs = other_attrs;
|
||||
if default {
|
||||
Ok(quote! { #[serde(default)] pub #typed_arg })
|
||||
} else {
|
||||
Ok(quote! { pub #typed_arg })
|
||||
}
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
let dummy = body.to_dummy_output();
|
||||
let dummy_name = body.to_dummy_ident();
|
||||
let args = syn::parse::<ServerFnArgs>(args.into())?;
|
||||
@@ -130,60 +178,11 @@ pub fn server_macro_impl(
|
||||
};
|
||||
|
||||
// build struct for type
|
||||
let mut body = body;
|
||||
let fn_name = &body.ident;
|
||||
let fn_name_as_str = body.ident.to_string();
|
||||
let vis = body.vis;
|
||||
let attrs = body.attrs;
|
||||
|
||||
let fields = body
|
||||
.inputs
|
||||
.iter_mut()
|
||||
.map(|f| {
|
||||
let typed_arg = match f {
|
||||
FnArg::Receiver(_) => {
|
||||
return Err(syn::Error::new(
|
||||
f.span(),
|
||||
"cannot use receiver types in server function macro",
|
||||
))
|
||||
}
|
||||
FnArg::Typed(t) => t,
|
||||
};
|
||||
|
||||
// strip `mut`, which is allowed in fn args but not in struct fields
|
||||
if let Pat::Ident(ident) = &mut *typed_arg.pat {
|
||||
ident.mutability = None;
|
||||
}
|
||||
|
||||
// allow #[server(default)] on fields — TODO is this documented?
|
||||
let mut default = false;
|
||||
let mut other_attrs = Vec::new();
|
||||
for attr in typed_arg.attrs.iter() {
|
||||
if !attr.path().is_ident("server") {
|
||||
other_attrs.push(attr.clone());
|
||||
continue;
|
||||
}
|
||||
attr.parse_nested_meta(|meta| {
|
||||
if meta.path.is_ident("default") && meta.input.is_empty() {
|
||||
default = true;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(meta.error(
|
||||
"Unrecognized #[server] attribute, expected \
|
||||
#[server(default)]",
|
||||
))
|
||||
}
|
||||
})?;
|
||||
}
|
||||
typed_arg.attrs = other_attrs;
|
||||
if default {
|
||||
Ok(quote! { #[serde(default)] pub #typed_arg })
|
||||
} else {
|
||||
Ok(quote! { pub #typed_arg })
|
||||
}
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
let fn_args = body
|
||||
.inputs
|
||||
.iter()
|
||||
@@ -637,18 +636,20 @@ fn err_type(return_ty: &Type) -> Result<Option<&GenericArgument>> {
|
||||
else if let GenericArgument::Type(Type::Path(pat)) =
|
||||
&args.args[1]
|
||||
{
|
||||
if pat.path.segments[0].ident == "ServerFnError" {
|
||||
let args = &pat.path.segments[0].arguments;
|
||||
match args {
|
||||
// Result<T, ServerFnError>
|
||||
PathArguments::None => return Ok(None),
|
||||
// Result<T, ServerFnError<E>>
|
||||
PathArguments::AngleBracketed(args) => {
|
||||
if args.args.len() == 1 {
|
||||
return Ok(Some(&args.args[0]));
|
||||
if let Some(segment) = pat.path.segments.last() {
|
||||
if segment.ident == "ServerFnError" {
|
||||
let args = &pat.path.segments[0].arguments;
|
||||
match args {
|
||||
// Result<T, ServerFnError>
|
||||
PathArguments::None => return Ok(None),
|
||||
// Result<T, ServerFnError<E>>
|
||||
PathArguments::AngleBracketed(args) => {
|
||||
if args.args.len() == 1 {
|
||||
return Ok(Some(&args.args[0]));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user