mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-28 11:21:55 -05:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bcaa102d8d | ||
|
|
8a2ae7fc7c | ||
|
|
9de34b74cf | ||
|
|
1b5961edaa | ||
|
|
26d1aee9ad | ||
|
|
2bf09384df | ||
|
|
ac12e1a411 | ||
|
|
b367b68a43 | ||
|
|
1f9dad421f | ||
|
|
4648fb2cfc | ||
|
|
817ec045f7 |
26
Cargo.toml
26
Cargo.toml
@@ -25,22 +25,22 @@ members = [
|
||||
exclude = ["benchmarks", "examples"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.6.0-rc1"
|
||||
version = "0.6.3"
|
||||
|
||||
[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.3" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.6.3" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.6.3" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.6.3" }
|
||||
leptos_reactive = { path = "./leptos_reactive", version = "0.6.3" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.6.3" }
|
||||
server_fn = { path = "./server_fn", version = "0.6.3" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.6.3" }
|
||||
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.3" }
|
||||
leptos_router = { path = "./router", version = "0.6.3" }
|
||||
leptos_meta = { path = "./meta", version = "0.6.3" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.6.3" }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
|
||||
57
README.md
57
README.md
@@ -40,6 +40,25 @@ pub fn SimpleCounter(initial_value: i32) -> impl IntoView {
|
||||
}
|
||||
}
|
||||
|
||||
// we also support a builder syntax rather than the JSX-like `view` macro
|
||||
#[component]
|
||||
pub fn SimpleCounterWithBuilder(initial_value: i32) -> impl IntoView {
|
||||
use leptos::html::*;
|
||||
|
||||
let (value, set_value) = create_signal(initial_value);
|
||||
let clear = move |_| set_value(0);
|
||||
let decrement = move |_| set_value.update(|value| *value -= 1);
|
||||
let increment = move |_| set_value.update(|value| *value += 1);
|
||||
|
||||
// the `view` macro above expands to this builder syntax
|
||||
div().child((
|
||||
button().on(ev::click, clear).child("Clear"),
|
||||
button().on(ev::click, decrement).child("-1"),
|
||||
span().child(("Value: ", value, "!")),
|
||||
button().on(ev::click, increment).child("+1")
|
||||
))
|
||||
}
|
||||
|
||||
// Easy to use with Trunk (trunkrs.dev) or with a simple wasm-bindgen setup
|
||||
pub fn main() {
|
||||
mount_to_body(|| view! {
|
||||
@@ -140,35 +159,27 @@ Sure! Obviously the `view` macro is for generating DOM nodes but you can use the
|
||||
|
||||
I've put together a [very simple GTK example](https://github.com/leptos-rs/leptos/blob/main/examples/gtk/src/main.rs) so you can see what I mean.
|
||||
|
||||
### How is this different from Yew/Dioxus?
|
||||
The new rendering approach being developed for 0.7 supports “universal rendering,” i.e., it can use any rendering library that supports a small set of 6-8 functions. (This is intended as a layer over typical retained-mode, OOP-style GUI toolkits like the DOM, GTK, etc.) That future rendering work will allow creating native UI in a way that is much more similar to the declarative approach used by the web framework.
|
||||
|
||||
On the surface level, these libraries may seem similar. Yew is, of course, the most mature Rust library for web UI development and has a huge ecosystem. Dioxus is similar in many ways, being heavily inspired by React. Here are some conceptual differences between Leptos and these frameworks:
|
||||
### How is this different from Yew?
|
||||
|
||||
Yew is the most-used library for Rust web UI development, but there are several differences between Yew and Leptos, in philosophy, approach, and performance.
|
||||
|
||||
- **VDOM vs. fine-grained:** Yew is built on the virtual DOM (VDOM) model: state changes cause components to re-render, generating a new virtual DOM tree. Yew diffs this against the previous VDOM, and applies those patches to the actual DOM. Component functions rerun whenever state changes. Leptos takes an entirely different approach. Components run once, creating (and returning) actual DOM nodes and setting up a reactive system to update those DOM nodes.
|
||||
- **Performance:** This has huge performance implications: Leptos is simply much faster at both creating and updating the UI than Yew is. (Dioxus has made huge advances in performance with its recent 0.3 release, and is now roughly on par with Leptos.)
|
||||
- **Mental model:** Adopting fine-grained reactivity also tends to simplify the mental model. There are no surprising component re-renders because there are no re-renders. You can call functions, create timeouts, etc. within the body of your component functions because they won’t be re-run. You don’t need to think about manual dependency tracking for effects; fine-grained reactivity tracks dependencies automatically.
|
||||
- **Performance:** This has huge performance implications: Leptos is simply much faster at both creating and updating the UI than Yew is.
|
||||
- **Server integration:** Yew was created in an era in which browser-rendered single-page apps (SPAs) were the dominant paradigm. While Leptos supports client-side rendering, it also focuses on integrating with the server side of your application via server functions and multiple modes of serving HTML, including out-of-order streaming.
|
||||
|
||||
### How is this different from Sycamore?
|
||||
- ### How is this different from Dioxus?
|
||||
|
||||
Conceptually, these two frameworks are very similar: because both are built on fine-grained reactivity, most apps will end up looking very similar between the two, and Sycamore or Leptos apps will both look a lot like SolidJS apps, in the same way that Yew or Dioxus can look a lot like React.
|
||||
Like Leptos, Dioxus is a framework for building UIs using web technologies. However, there are significant differences in approach and features.
|
||||
|
||||
There are some practical differences that make a significant difference:
|
||||
- **VDOM vs. fine-grained:** While Dioxus has a performant virtual DOM (VDOM), it still uses coarse-grained/component-scoped reactivity: changing a stateful value reruns the component function and diffs the old UI against the new one. Leptos components use a different mental model, creating (and returning) actual DOM nodes and setting up a reactive system to update those DOM nodes.
|
||||
- **Web vs. desktop priorities:** Dioxus uses Leptos server functions in its fullstack mode, but does not have the same `<Suspense>`-based support for things like streaming HTML rendering, or share the same focus on holistic web performance. Leptos tends to prioritize holistic web performance (streaming HTML rendering, smaller WASM binary sizes, etc.), whereas Dioxus has an unparalleled experience when building desktop apps, because your application logic runs as a native Rust binary.
|
||||
|
||||
- **Templating:** Leptos uses a JSX-like template format (built on [syn-rsx](https://github.com/stoically/syn-rsx)) for its `view` macro. Sycamore offers the choice of its own templating DSL or a builder syntax.
|
||||
- **Server integration:** Leptos provides primitives that encourage HTML streaming and allow for easy async integration and RPC calls, even without WASM enabled, making it easy to opt into integrations between your frontend and backend code without pushing you toward any particular metaframework patterns.
|
||||
- **Read-write segregation:** Leptos, like Solid, encourages read-write segregation between signal getters and setters, so you end up accessing signals with tuples like `let (count, set_count) = create_signal(0);` _(If you prefer or if it's more convenient for your API, you can use [`create_rw_signal`](https://docs.rs/leptos/latest/leptos/fn.create_rw_signal.html) to give a unified read/write signal.)_
|
||||
- **Signals are functions:** In Leptos, you can call a signal to access it rather than calling a specific method (so, `count()` instead of `count.get()`) This creates a more consistent mental model: accessing a reactive value is always a matter of calling a function. For example:
|
||||
- ### How is this different from Sycamore?
|
||||
|
||||
```rust
|
||||
let (count, set_count) = create_signal(0); // a signal
|
||||
let double_count = move || count() * 2; // a derived signal
|
||||
let memoized_count = create_memo(move |_| count() * 3); // a memo
|
||||
// all are accessed by calling them
|
||||
assert_eq!(count(), 0);
|
||||
assert_eq!(double_count(), 0);
|
||||
assert_eq!(memoized_count(), 0);
|
||||
// this function can accept any of those signals
|
||||
fn do_work_on_signal(my_signal: impl Fn() -> i32) { ... }
|
||||
```
|
||||
Sycamore and Leptos are both heavily influenced by SolidJS. At this point, Leptos has a larger community and ecosystem and is more actively developed. Other differences:
|
||||
|
||||
- **Signals and scopes are `'static`:** Both Leptos and Sycamore ease the pain of moving signals in closures (in particular, event listeners) by making them `Copy`, to avoid the `{ let count = count.clone(); move |_| ... }` that's very familiar in Rust UI code. Sycamore does this by using bump allocation to tie the lifetimes of its signals to its scopes: since references are `Copy`, `&'a Signal<T>` can be moved into a closure. Leptos does this by using arena allocation and passing around indices: types like `ReadSignal<T>`, `WriteSignal<T>`, and `Memo<T>` are actually wrappers for indices into an arena. This means that both scopes and signals are both `Copy` and `'static` in Leptos, which means that they can be moved easily into closures without adding lifetime complexity.
|
||||
- **Templating DSLs:** Sycamore uses a custom templating language for its views, while Leptos uses a JSX-like template format.
|
||||
- **`'static` signals:** One of Leptos’s main innovations was the creation of `Copy + 'static` signals, which have excellent ergonomics. Sycamore is in the process of adopting the same pattern, but this is not yet released.
|
||||
- **Perseus vs. server functions:** The Perseus metaframework provides an opinionated way to build Sycamore apps that include server functionality. Leptos instead provides primitives like server functions in the core of the framework.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -6,7 +6,7 @@ use convert_case::{
|
||||
use itertools::Itertools;
|
||||
use leptos_hot_reload::parsing::value_to_string;
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use quote::{format_ident, quote_spanned, ToTokens, TokenStreamExt};
|
||||
use quote::{format_ident, quote, quote_spanned, ToTokens, TokenStreamExt};
|
||||
use syn::{
|
||||
parse::Parse, parse_quote, spanned::Spanned,
|
||||
AngleBracketedGenericArguments, Attribute, FnArg, GenericArgument, Item,
|
||||
|
||||
@@ -12,7 +12,7 @@ extern crate proc_macro_error;
|
||||
use component::DummyModel;
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::{Span, TokenTree};
|
||||
use quote::ToTokens;
|
||||
use quote::{quote, ToTokens};
|
||||
use rstml::{node::KeyedAttribute, parse};
|
||||
use syn::{parse_macro_input, spanned::Spanned, token::Pub, Visibility};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::component::{
|
||||
};
|
||||
use attribute_derive::Attribute as AttributeDerive;
|
||||
use proc_macro2::{Ident, TokenStream};
|
||||
use quote::{ToTokens, TokenStreamExt};
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use syn::{
|
||||
parse::Parse, parse_quote, Field, ItemStruct, LitStr, Meta, Type,
|
||||
Visibility,
|
||||
|
||||
@@ -385,7 +385,10 @@ where
|
||||
// client
|
||||
create_render_effect({
|
||||
let r = Rc::clone(&r);
|
||||
move |_| r.load(false, id)
|
||||
move |_| {
|
||||
source.track();
|
||||
r.load(false, id)
|
||||
}
|
||||
});
|
||||
|
||||
Resource {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_meta"
|
||||
version = "0.6.0-rc1"
|
||||
version = "0.6.3"
|
||||
edition = "2021"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "leptos_router"
|
||||
version = "0.6.0-rc1"
|
||||
version = "0.6.3"
|
||||
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