From 0cb921c1581197ed2f27b0e8f639e368055c3398 Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Sun, 25 Jun 2023 15:18:00 -0400 Subject: [PATCH] feat: add an `anyhow`-like `Result` type for easier error handling (#1228) --- examples/fetch/src/lib.rs | 18 +-- examples/session_auth_axum/src/auth.rs | 24 ++-- examples/session_auth_axum/src/todo.rs | 17 +-- examples/todo_app_sqlite/src/todo.rs | 13 +- examples/todo_app_sqlite_axum/src/todo.rs | 13 +- examples/todo_app_sqlite_viz/src/todo.rs | 13 +- leptos/src/lib.rs | 6 + leptos_dom/Cargo.toml | 1 + leptos_dom/src/components/errors.rs | 43 +++--- leptos_server/src/lib.rs | 4 +- router/src/components/form.rs | 7 +- server_fn/src/error.rs | 167 ++++++++++++++++++++++ server_fn/src/lib.rs | 34 +---- 13 files changed, 238 insertions(+), 122 deletions(-) create mode 100644 server_fn/src/error.rs diff --git a/examples/fetch/src/lib.rs b/examples/fetch/src/lib.rs index d4b50f1c07..9a90377496 100644 --- a/examples/fetch/src/lib.rs +++ b/examples/fetch/src/lib.rs @@ -1,4 +1,4 @@ -use leptos::*; +use leptos::{error::Result, *}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -8,30 +8,24 @@ pub struct Cat { } #[derive(Error, Clone, Debug)] -pub enum FetchError { +pub enum CatError { #[error("Please request more than zero cats.")] NonZeroCats, - #[error("Error loading data from serving.")] - Request, - #[error("Error deserializaing cat data from request.")] - Json, } type CatCount = usize; -async fn fetch_cats(count: CatCount) -> Result, FetchError> { +async fn fetch_cats(count: CatCount) -> Result> { if count > 0 { // make the request let res = reqwasm::http::Request::get(&format!( "https://api.thecatapi.com/v1/images/search?limit={count}", )) .send() - .await - .map_err(|_| FetchError::Request)? + .await? // convert it to JSON .json::>() - .await - .map_err(|_| FetchError::Json)? + .await? // extract the URL field for each cat .into_iter() .take(count) @@ -39,7 +33,7 @@ async fn fetch_cats(count: CatCount) -> Result, FetchError> { .collect::>(); Ok(res) } else { - Err(FetchError::NonZeroCats) + Err(CatError::NonZeroCats.into()) } } diff --git a/examples/session_auth_axum/src/auth.rs b/examples/session_auth_axum/src/auth.rs index b96d360ebe..3334f32382 100644 --- a/examples/session_auth_axum/src/auth.rs +++ b/examples/session_auth_axum/src/auth.rs @@ -163,12 +163,11 @@ pub async fn login( let user: User = User::get_from_username(username, &pool) .await - .ok_or("User does not exist.") - .map_err(|e| ServerFnError::ServerError(e.to_string()))?; + .ok_or_else(|| { + ServerFnError::ServerError("User does not exist.".into()) + })?; - match verify(password, &user.password) - .map_err(|e| ServerFnError::ServerError(e.to_string()))? - { + match verify(password, &user.password)? { true => { auth.login_user(user.id); auth.remember_user(remember.is_some()); @@ -204,13 +203,16 @@ pub async fn signup( .bind(username.clone()) .bind(password_hashed) .execute(&pool) - .await - .map_err(|e| ServerFnError::ServerError(e.to_string()))?; + .await?; - let user = User::get_from_username(username, &pool) - .await - .ok_or("Signup failed: User does not exist.") - .map_err(|e| ServerFnError::ServerError(e.to_string()))?; + let user = + User::get_from_username(username, &pool) + .await + .ok_or_else(|| { + ServerFnError::ServerError( + "Signup failed: User does not exist.".into(), + ) + })?; auth.login_user(user.id); auth.remember_user(remember.is_some()); diff --git a/examples/session_auth_axum/src/todo.rs b/examples/session_auth_axum/src/todo.rs index 9d195472c8..afd54a5ed5 100644 --- a/examples/session_auth_axum/src/todo.rs +++ b/examples/session_auth_axum/src/todo.rs @@ -21,14 +21,12 @@ if #[cfg(feature = "ssr")] { pub fn pool(cx: Scope) -> Result { use_context::(cx) - .ok_or("Pool missing.") - .map_err(|e| ServerFnError::ServerError(e.to_string())) + .ok_or_else(|| ServerFnError::ServerError("Pool missing.".into())) } pub fn auth(cx: Scope) -> Result { use_context::(cx) - .ok_or("Auth session missing.") - .map_err(|e| ServerFnError::ServerError(e.to_string())) + .ok_or_else(|| ServerFnError::ServerError("Auth session missing.".into())) } #[derive(sqlx::FromRow, Clone)] @@ -64,11 +62,7 @@ pub async fn get_todos(cx: Scope) -> Result, ServerFnError> { let mut rows = sqlx::query_as::<_, SqlTodo>("SELECT * FROM todos").fetch(&pool); - while let Some(row) = rows - .try_next() - .await - .map_err(|e| ServerFnError::ServerError(e.to_string()))? - { + while let Some(row) = rows.try_next().await? { todos.push(row); } @@ -117,12 +111,11 @@ pub async fn add_todo(cx: Scope, title: String) -> Result<(), ServerFnError> { pub async fn delete_todo(cx: Scope, id: u16) -> Result<(), ServerFnError> { let pool = pool(cx)?; - sqlx::query("DELETE FROM todos WHERE id = $1") + Ok(sqlx::query("DELETE FROM todos WHERE id = $1") .bind(id) .execute(&pool) .await - .map(|_| ()) - .map_err(|e| ServerFnError::ServerError(e.to_string())) + .map(|_| ())?) } #[component] diff --git a/examples/todo_app_sqlite/src/todo.rs b/examples/todo_app_sqlite/src/todo.rs index 1e98f4193e..f8ebe7d8f8 100644 --- a/examples/todo_app_sqlite/src/todo.rs +++ b/examples/todo_app_sqlite/src/todo.rs @@ -9,7 +9,7 @@ cfg_if! { use sqlx::{Connection, SqliteConnection}; pub async fn db() -> Result { - SqliteConnection::connect("sqlite:Todos.db").await.map_err(|e| ServerFnError::ServerError(e.to_string())) + Ok(SqliteConnection::connect("sqlite:Todos.db").await?) } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)] @@ -43,11 +43,7 @@ pub async fn get_todos(cx: Scope) -> Result, ServerFnError> { let mut todos = Vec::new(); let mut rows = sqlx::query_as::<_, Todo>("SELECT * FROM todos").fetch(&mut conn); - while let Some(row) = rows - .try_next() - .await - .map_err(|e| ServerFnError::ServerError(e.to_string()))? - { + while let Some(row) = rows.try_next().await? { todos.push(row); } @@ -76,12 +72,11 @@ pub async fn add_todo(title: String) -> Result<(), ServerFnError> { pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> { let mut conn = db().await?; - sqlx::query("DELETE FROM todos WHERE id = $1") + Ok(sqlx::query("DELETE FROM todos WHERE id = $1") .bind(id) .execute(&mut conn) .await - .map(|_| ()) - .map_err(|e| ServerFnError::ServerError(e.to_string())) + .map(|_| ())?) } #[component] diff --git a/examples/todo_app_sqlite_axum/src/todo.rs b/examples/todo_app_sqlite_axum/src/todo.rs index d603f2dad4..e5492aa744 100644 --- a/examples/todo_app_sqlite_axum/src/todo.rs +++ b/examples/todo_app_sqlite_axum/src/todo.rs @@ -11,7 +11,7 @@ cfg_if! { // use http::{header::SET_COOKIE, HeaderMap, HeaderValue, StatusCode}; pub async fn db() -> Result { - SqliteConnection::connect("sqlite:Todos.db").await.map_err(|e| ServerFnError::ServerError(e.to_string())) + Ok(SqliteConnection::connect("sqlite:Todos.db").await?) } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)] @@ -47,11 +47,7 @@ pub async fn get_todos(cx: Scope) -> Result, ServerFnError> { let mut todos = Vec::new(); let mut rows = sqlx::query_as::<_, Todo>("SELECT * FROM todos").fetch(&mut conn); - while let Some(row) = rows - .try_next() - .await - .map_err(|e| ServerFnError::ServerError(e.to_string()))? - { + while let Some(row) = rows.try_next().await? { todos.push(row); } @@ -93,12 +89,11 @@ pub async fn add_todo(title: String) -> Result<(), ServerFnError> { pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> { let mut conn = db().await?; - sqlx::query("DELETE FROM todos WHERE id = $1") + Ok(sqlx::query("DELETE FROM todos WHERE id = $1") .bind(id) .execute(&mut conn) .await - .map(|_| ()) - .map_err(|e| ServerFnError::ServerError(e.to_string())) + .map(|_| ())?) } #[component] diff --git a/examples/todo_app_sqlite_viz/src/todo.rs b/examples/todo_app_sqlite_viz/src/todo.rs index fda62c4949..f9a987c6f1 100644 --- a/examples/todo_app_sqlite_viz/src/todo.rs +++ b/examples/todo_app_sqlite_viz/src/todo.rs @@ -11,7 +11,7 @@ cfg_if! { // use http::{header::SET_COOKIE, HeaderMap, HeaderValue, StatusCode}; pub async fn db() -> Result { - SqliteConnection::connect("sqlite:Todos.db").await.map_err(|e| ServerFnError::ServerError(e.to_string())) + Ok(SqliteConnection::connect("sqlite:Todos.db").await?) } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)] @@ -47,11 +47,7 @@ pub async fn get_todos(cx: Scope) -> Result, ServerFnError> { let mut todos = Vec::new(); let mut rows = sqlx::query_as::<_, Todo>("SELECT * FROM todos").fetch(&mut conn); - while let Some(row) = rows - .try_next() - .await - .map_err(|e| ServerFnError::ServerError(e.to_string()))? - { + while let Some(row) = rows.try_next().await? { todos.push(row); } @@ -93,12 +89,11 @@ pub async fn add_todo(title: String) -> Result<(), ServerFnError> { pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> { let mut conn = db().await?; - sqlx::query("DELETE FROM todos WHERE id = $1") + Ok(sqlx::query("DELETE FROM todos WHERE id = $1") .bind(id) .execute(&mut conn) .await - .map(|_| ()) - .map_err(|e| ServerFnError::ServerError(e.to_string())) + .map(|_| ())?) } #[component] diff --git a/leptos/src/lib.rs b/leptos/src/lib.rs index 7337f7b849..7e20c5c09d 100644 --- a/leptos/src/lib.rs +++ b/leptos/src/lib.rs @@ -171,6 +171,11 @@ pub use leptos_dom::{ Class, CollectView, Errors, Fragment, HtmlElement, IntoAttribute, IntoClass, IntoProperty, IntoStyle, IntoView, NodeRef, Property, View, }; + +/// Types to make it easier to handle errors in your application. +pub mod error { + pub use server_fn::error::{Error, Result}; +} #[cfg(not(any(target_arch = "wasm32", feature = "template_macro")))] pub use leptos_macro::view as template; pub use leptos_macro::{component, server, slot, view, Params}; @@ -178,6 +183,7 @@ pub use leptos_reactive::*; pub use leptos_server::{ self, create_action, create_multi_action, create_server_action, create_server_multi_action, Action, MultiAction, ServerFn, ServerFnError, + ServerFnErrorErr, }; pub use server_fn::{self, ServerFn as _}; pub use typed_builder; diff --git a/leptos_dom/Cargo.toml b/leptos_dom/Cargo.toml index 43ddb35b28..2be5c473f1 100644 --- a/leptos_dom/Cargo.toml +++ b/leptos_dom/Cargo.toml @@ -18,6 +18,7 @@ indexmap = "1.9" itertools = "0.10" js-sys = "0.3" leptos_reactive = { workspace = true } +server_fn = { workspace = true } once_cell = "1" pad-adapter = "0.1" paste = "1" diff --git a/leptos_dom/src/components/errors.rs b/leptos_dom/src/components/errors.rs index 6717e84c18..b3a3649ac0 100644 --- a/leptos_dom/src/components/errors.rs +++ b/leptos_dom/src/components/errors.rs @@ -1,12 +1,13 @@ use crate::{HydrationCtx, IntoView}; use cfg_if::cfg_if; use leptos_reactive::{signal_prelude::*, use_context, RwSignal}; -use std::{borrow::Cow, collections::HashMap, error::Error, sync::Arc}; +use server_fn::error::Error; +use std::{borrow::Cow, collections::HashMap}; /// A struct to hold all the possible errors that could be provided by child Views #[derive(Debug, Clone, Default)] #[repr(transparent)] -pub struct Errors(HashMap>); +pub struct Errors(HashMap); /// A unique key for an error that occurs at a particular location in the user interface. #[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] @@ -24,7 +25,7 @@ where } impl IntoIterator for Errors { - type Item = (ErrorKey, Arc); + type Item = (ErrorKey, Error); type IntoIter = IntoIter; #[inline(always)] @@ -35,15 +36,10 @@ impl IntoIterator for Errors { /// An owning iterator over all the errors contained in the [Errors] struct. #[repr(transparent)] -pub struct IntoIter( - std::collections::hash_map::IntoIter< - ErrorKey, - Arc, - >, -); +pub struct IntoIter(std::collections::hash_map::IntoIter); impl Iterator for IntoIter { - type Item = (ErrorKey, Arc); + type Item = (ErrorKey, Error); #[inline(always)] fn next( @@ -55,16 +51,10 @@ impl Iterator for IntoIter { /// An iterator over all the errors contained in the [Errors] struct. #[repr(transparent)] -pub struct Iter<'a>( - std::collections::hash_map::Iter< - 'a, - ErrorKey, - Arc, - >, -); +pub struct Iter<'a>(std::collections::hash_map::Iter<'a, ErrorKey, Error>); impl<'a> Iterator for Iter<'a> { - type Item = (&'a ErrorKey, &'a Arc); + type Item = (&'a ErrorKey, &'a Error); #[inline(always)] fn next( @@ -77,7 +67,7 @@ impl<'a> Iterator for Iter<'a> { impl IntoView for Result where T: IntoView + 'static, - E: Error + Send + Sync + 'static, + E: Into, { fn into_view(self, cx: leptos_reactive::Scope) -> crate::View { let id = ErrorKey(HydrationCtx::peek().fragment.to_string().into()); @@ -92,6 +82,7 @@ where stuff.into_view(cx) } Err(error) => { + let error = error.into(); match errors { Some(errors) => { errors.update({ @@ -133,6 +124,7 @@ where } } } + impl Errors { /// Returns `true` if there are no errors. #[inline(always)] @@ -143,24 +135,21 @@ impl Errors { /// Add an error to Errors that will be processed by `` pub fn insert(&mut self, key: ErrorKey, error: E) where - E: Error + Send + Sync + 'static, + E: Into, { - self.0.insert(key, Arc::new(error)); + self.0.insert(key, error.into()); } /// Add an error with the default key for errors outside the reactive system pub fn insert_with_default_key(&mut self, error: E) where - E: Error + Send + Sync + 'static, + E: Into, { - self.0.insert(Default::default(), Arc::new(error)); + self.0.insert(Default::default(), error.into()); } /// Remove an error to Errors that will be processed by `` - pub fn remove( - &mut self, - key: &ErrorKey, - ) -> Option> { + pub fn remove(&mut self, key: &ErrorKey) -> Option { self.0.remove(key) } diff --git a/leptos_server/src/lib.rs b/leptos_server/src/lib.rs index 5d6d0b31ff..186fc01a5c 100644 --- a/leptos_server/src/lib.rs +++ b/leptos_server/src/lib.rs @@ -116,7 +116,9 @@ //! your app is not available. use leptos_reactive::*; -pub use server_fn::{Encoding, Payload, ServerFnError}; +pub use server_fn::{ + error::ServerFnErrorErr, Encoding, Payload, ServerFnError, +}; mod action; mod multi_action; diff --git a/router/src/components/form.rs b/router/src/components/form.rs index 1be6c628e1..4e3dd95819 100644 --- a/router/src/components/form.rs +++ b/router/src/components/form.rs @@ -386,7 +386,7 @@ where let e = ServerFnError::Request(e.to_string()); value.try_set(Some(Err(e.clone()))); if let Some(error) = error { - error.try_set(Some(Box::new(e))); + error.try_set(Some(Box::new(ServerFnErrorErr::from(e)))); } }); }); @@ -406,7 +406,8 @@ where cx.batch(move || { value.try_set(Some(Err(e.clone()))); if let Some(error) = error { - error.try_set(Some(Box::new(e))); + error + .try_set(Some(Box::new(ServerFnErrorErr::from(e)))); } }); } @@ -472,7 +473,7 @@ where error!("{e:?}"); if let Some(error) = error { error.try_set(Some(Box::new( - ServerFnError::Request( + ServerFnErrorErr::Request( e.as_string().unwrap_or_default(), ), ))); diff --git a/server_fn/src/error.rs b/server_fn/src/error.rs new file mode 100644 index 0000000000..60a39c91b7 --- /dev/null +++ b/server_fn/src/error.rs @@ -0,0 +1,167 @@ +use serde::{Deserialize, Serialize}; +use std::{error, fmt, ops, sync::Arc}; +use thiserror::Error; + +/// This is a result type into which any error can be converted, +/// and which can be used directly in your `view`. +/// +/// All errors will be stored as [`Error`]. +pub type Result = core::result::Result; + +/// A generic wrapper for any error. +#[derive(Debug, Clone)] +#[repr(transparent)] +pub struct Error(Arc); + +impl Error { + /// Converts the wrapper into the inner reference-counted error. + pub fn into_inner(self) -> Arc { + Arc::clone(&self.0) + } +} + +impl ops::Deref for Error { + type Target = Arc; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for Error +where + T: std::error::Error + Send + Sync + 'static, +{ + fn from(value: T) -> Self { + Error(Arc::new(value)) + } +} + +impl From for Error { + fn from(e: ServerFnError) -> Self { + Error(Arc::new(ServerFnErrorErr::from(e))) + } +} + +/// Type for errors that can occur when using server functions. +/// +/// Unlike [`ServerFnErrorErr`], this does not implement [`std::error::Error`]. +/// This means that other error types can easily be converted into it using the +/// `?` operator. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ServerFnError { + /// Error while trying to register the server function (only occurs in case of poisoned RwLock). + Registration(String), + /// Occurs on the client if there is a network error while trying to run function on server. + Request(String), + /// Occurs when there is an error while actually running the function on the server. + ServerError(String), + /// Occurs on the client if there is an error deserializing the server's response. + Deserialization(String), + /// Occurs on the client if there is an error serializing the server function arguments. + Serialization(String), + /// Occurs on the server if there is an error deserializing one of the arguments that's been sent. + Args(String), + /// Occurs on the server if there's a missing argument. + MissingArg(String), +} + +impl std::fmt::Display for ServerFnError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match self { + ServerFnError::Registration(s) => format!( + "error while trying to register the server function: {s}" + ), + ServerFnError::Request(s) => format!( + "error reaching server to call server function: {s}" + ), + ServerFnError::ServerError(s) => + format!("error running server function: {s}"), + ServerFnError::Deserialization(s) => + format!("error deserializing server function results: {s}"), + ServerFnError::Serialization(s) => + format!("error serializing server function arguments: {s}"), + ServerFnError::Args(s) => format!( + "error deserializing server function arguments: {s}" + ), + ServerFnError::MissingArg(s) => format!("missing argument {s}"), + } + ) + } +} + +impl From for ServerFnError +where + E: std::error::Error, +{ + fn from(e: E) -> Self { + ServerFnError::ServerError(e.to_string()) + } +} + +/// Type for errors that can occur when using server functions. +/// +/// Unlike [`ServerFnErrorErr`], this implements [`std::error::Error`]. This means +/// it can be used in situations in which the `Error` trait is required, but it’s +/// not possible to create a blanket implementation that converts other errors into +/// this type. +/// +/// [`ServerFnError`] and [`ServerFnErrorErr`] mutually implement [`From`], so +/// it is easy to convert between the two types. +#[derive(Error, Debug, Clone, Serialize, Deserialize)] +pub enum ServerFnErrorErr { + /// Error while trying to register the server function (only occurs in case of poisoned RwLock). + #[error("error while trying to register the server function: {0}")] + Registration(String), + /// Occurs on the client if there is a network error while trying to run function on server. + #[error("error reaching server to call server function: {0}")] + Request(String), + /// Occurs when there is an error while actually running the function on the server. + #[error("error running server function: {0}")] + ServerError(String), + /// Occurs on the client if there is an error deserializing the server's response. + #[error("error deserializing server function results: {0}")] + Deserialization(String), + /// Occurs on the client if there is an error serializing the server function arguments. + #[error("error serializing server function arguments: {0}")] + Serialization(String), + /// Occurs on the server if there is an error deserializing one of the arguments that's been sent. + #[error("error deserializing server function arguments: {0}")] + Args(String), + /// Occurs on the server if there's a missing argument. + #[error("missing argument {0}")] + MissingArg(String), +} + +impl From for ServerFnErrorErr { + fn from(value: ServerFnError) -> Self { + match value { + ServerFnError::Registration(value) => { + ServerFnErrorErr::Registration(value) + } + ServerFnError::Request(value) => ServerFnErrorErr::Request(value), + ServerFnError::ServerError(value) => { + ServerFnErrorErr::ServerError(value) + } + ServerFnError::Deserialization(value) => { + ServerFnErrorErr::Deserialization(value) + } + ServerFnError::Serialization(value) => { + ServerFnErrorErr::Serialization(value) + } + ServerFnError::Args(value) => ServerFnErrorErr::Args(value), + ServerFnError::MissingArg(value) => { + ServerFnErrorErr::MissingArg(value) + } + } + } +} diff --git a/server_fn/src/lib.rs b/server_fn/src/lib.rs index 60c66d3a2a..35752c792a 100644 --- a/server_fn/src/lib.rs +++ b/server_fn/src/lib.rs @@ -90,15 +90,17 @@ use quote::TokenStreamExt; // used by the macro #[doc(hidden)] pub use serde; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Serialize}; pub use server_fn_macro_default::server; use std::{future::Future, pin::Pin, str::FromStr}; #[cfg(any(feature = "ssr", doc))] use syn::parse_quote; -use thiserror::Error; // used by the macro #[doc(hidden)] pub use xxhash_rust; +/// Error types used in server functions. +pub mod error; +pub use error::ServerFnError; /// Default server function registry pub mod default; @@ -451,32 +453,6 @@ where } } -/// Type for errors that can occur when using server functions. -#[derive(Error, Debug, Clone, Serialize, Deserialize)] -pub enum ServerFnError { - /// Error while trying to register the server function (only occurs in case of poisoned RwLock). - #[error("error while trying to register the server function: {0}")] - Registration(String), - /// Occurs on the client if there is a network error while trying to run function on server. - #[error("error reaching server to call server function: {0}")] - Request(String), - /// Occurs when there is an error while actually running the function on the server. - #[error("error running server function: {0}")] - ServerError(String), - /// Occurs on the client if there is an error deserializing the server's response. - #[error("error deserializing server function results {0}")] - Deserialization(String), - /// Occurs on the client if there is an error serializing the server function arguments. - #[error("error serializing server function arguments {0}")] - Serialization(String), - /// Occurs on the server if there is an error deserializing one of the arguments that's been sent. - #[error("error deserializing server function arguments {0}")] - Args(String), - /// Occurs on the server if there's a missing argument. - #[error("missing argument {0}")] - MissingArg(String), -} - /// Executes the HTTP call to call a server function from the client, given its URL and argument type. #[cfg(not(feature = "ssr"))] pub async fn call_server_fn( @@ -649,7 +625,7 @@ where // Lazily initialize the client to be reused for all server function calls. #[cfg(any(all(not(feature = "ssr"), not(target_arch = "wasm32")), doc))] static CLIENT: once_cell::sync::Lazy = - once_cell::sync::Lazy::new(|| reqwest::Client::new()); + once_cell::sync::Lazy::new(reqwest::Client::new); #[cfg(any(all(not(feature = "ssr"), not(target_arch = "wasm32")), doc))] static ROOT_URL: once_cell::sync::OnceCell<&'static str> =