Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow any type that implements FromServerFnError as a replacement of the ServerFnError in server_fn #3274

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

106 changes: 77 additions & 29 deletions examples/server_fns_axum/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ use server_fn::{
MultipartFormData, Postcard, Rkyv, SerdeLite, StreamingText,
TextStream,
},
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
request::{browser::BrowserRequest, ClientReq, Req},
response::{browser::BrowserResponse, ClientRes, Res},
response::{browser::BrowserResponse, ClientRes, TryRes},
};
use std::future::Future;
#[cfg(feature = "ssr")]
Expand Down Expand Up @@ -652,32 +653,72 @@ pub fn FileWatcher() -> impl IntoView {
/// implementations if you'd like. However, it's much lighter weight to use something like `strum`
/// simply to generate those trait implementations.
#[server]
pub async fn ascii_uppercase(
text: String,
) -> Result<String, ServerFnError<InvalidArgument>> {
pub async fn ascii_uppercase(text: String) -> Result<String, MyErrors> {
other_error()?;
Ok(ascii_uppercase_inner(text)?)
}

pub fn other_error() -> Result<(), String> {
Ok(())
}

pub fn ascii_uppercase_inner(text: String) -> Result<String, InvalidArgument> {
if text.len() < 5 {
Err(InvalidArgument::TooShort.into())
Err(InvalidArgument::TooShort)
} else if text.len() > 15 {
Err(InvalidArgument::TooLong.into())
Err(InvalidArgument::TooLong)
} else if text.is_ascii() {
Ok(text.to_ascii_uppercase())
} else {
Err(InvalidArgument::NotAscii.into())
Err(InvalidArgument::NotAscii)
}
}

#[server]
pub async fn ascii_uppercase_classic(
text: String,
) -> Result<String, ServerFnError<InvalidArgument>> {
Ok(ascii_uppercase_inner(text)?)
}

// The EnumString and Display derive macros are provided by strum
#[derive(Debug, Clone, EnumString, Display)]
#[derive(Debug, Clone, Display, EnumString, Serialize, Deserialize)]
pub enum InvalidArgument {
TooShort,
TooLong,
NotAscii,
}

#[derive(Debug, Clone, Display, Serialize, Deserialize)]
pub enum MyErrors {
InvalidArgument(InvalidArgument),
ServerFnError(ServerFnErrorErr),
Other(String),
}

impl From<InvalidArgument> for MyErrors {
fn from(value: InvalidArgument) -> Self {
MyErrors::InvalidArgument(value)
}
}

impl From<String> for MyErrors {
fn from(value: String) -> Self {
MyErrors::Other(value)
}
}

impl FromServerFnError for MyErrors {
fn from_server_fn_error(value: ServerFnErrorErr) -> Self {
MyErrors::ServerFnError(value)
}
}
Comment on lines +692 to +715
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the new way to define a custom error type for server fns. It can used directly in the signature of server fn like -> Result<(), MyErrors> instead of -> Result<(), ServerFnError<MyErrors>.


#[component]
pub fn CustomErrorTypes() -> impl IntoView {
let input_ref = NodeRef::<Input>::new();
let (result, set_result) = signal(None);
let (result_classic, set_result_classic) = signal(None);

view! {
<h3>Using custom error types</h3>
Expand All @@ -692,14 +733,17 @@ pub fn CustomErrorTypes() -> impl IntoView {
<button on:click=move |_| {
let value = input_ref.get().unwrap().value();
spawn_local(async move {
let data = ascii_uppercase(value).await;
let data = ascii_uppercase(value.clone()).await;
let data_classic = ascii_uppercase_classic(value).await;
set_result.set(Some(data));
set_result_classic.set(Some(data_classic));
});
}>

"Submit"
</button>
<p>{move || format!("{:?}", result.get())}</p>
<p>{move || format!("{:?}", result_classic.get())}</p>
}
}

Expand All @@ -726,14 +770,12 @@ impl<T, Request, Err> IntoReq<Toml, Request, Err> for TomlEncoded<T>
where
Request: ClientReq<Err>,
T: Serialize,
Err: FromServerFnError,
{
fn into_req(
self,
path: &str,
accepts: &str,
) -> Result<Request, ServerFnError<Err>> {
let data = toml::to_string(&self.0)
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
fn into_req(self, path: &str, accepts: &str) -> Result<Request, Err> {
let data = toml::to_string(&self.0).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Request::try_new_post(path, Toml::CONTENT_TYPE, accepts, data)
}
}
Expand All @@ -742,23 +784,26 @@ impl<T, Request, Err> FromReq<Toml, Request, Err> for TomlEncoded<T>
where
Request: Req<Err> + Send,
T: DeserializeOwned,
Err: FromServerFnError,
{
async fn from_req(req: Request) -> Result<Self, ServerFnError<Err>> {
async fn from_req(req: Request) -> Result<Self, Err> {
let string_data = req.try_into_string().await?;
toml::from_str::<T>(&string_data)
.map(TomlEncoded)
.map_err(|e| ServerFnError::Args(e.to_string()))
.map_err(|e| ServerFnErrorErr::Args(e.to_string()).into_app_error())
}
}

impl<T, Response, Err> IntoRes<Toml, Response, Err> for TomlEncoded<T>
where
Response: Res<Err>,
Response: TryRes<Err>,
T: Serialize + Send,
Err: FromServerFnError,
{
async fn into_res(self) -> Result<Response, ServerFnError<Err>> {
let data = toml::to_string(&self.0)
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
async fn into_res(self) -> Result<Response, Err> {
let data = toml::to_string(&self.0).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Response::try_from_string(Toml::CONTENT_TYPE, data)
}
}
Expand All @@ -767,12 +812,13 @@ impl<T, Response, Err> FromRes<Toml, Response, Err> for TomlEncoded<T>
where
Response: ClientRes<Err> + Send,
T: DeserializeOwned,
Err: FromServerFnError,
{
async fn from_res(res: Response) -> Result<Self, ServerFnError<Err>> {
async fn from_res(res: Response) -> Result<Self, Err> {
let data = res.try_into_string().await?;
toml::from_str(&data)
.map(TomlEncoded)
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
toml::from_str(&data).map(TomlEncoded).map_err(|e| {
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()
})
}
}

Expand Down Expand Up @@ -835,7 +881,10 @@ pub fn CustomClientExample() -> impl IntoView {
pub struct CustomClient;

// Implement the `Client` trait for it.
impl<CustErr> Client<CustErr> for CustomClient {
impl<E> Client<E> for CustomClient
where
E: FromServerFnError,
{
// BrowserRequest and BrowserResponse are the defaults used by other server functions.
// They are wrappers for the underlying Web Fetch API types.
type Request = BrowserRequest;
Expand All @@ -844,8 +893,7 @@ pub fn CustomClientExample() -> impl IntoView {
// Our custom `send()` implementation does all the work.
fn send(
req: Self::Request,
) -> impl Future<Output = Result<Self::Response, ServerFnError<CustErr>>>
+ Send {
) -> impl Future<Output = Result<Self::Response, E>> + Send {
// BrowserRequest derefs to the underlying Request type from gloo-net,
// so we can get access to the headers here
let headers = req.headers();
Expand Down
1 change: 0 additions & 1 deletion integrations/actix/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,6 @@ pub fn handle_server_fns_with_context(
// actually run the server fn
let mut res = ActixResponse(
service
.0
.run(ActixRequest::from((req, payload)))
.await
.take(),
Expand Down
2 changes: 0 additions & 2 deletions integrations/axum/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -369,8 +369,6 @@
additional_context: impl Fn() + 'static + Clone + Send,
req: Request<Body>,
) -> impl IntoResponse {
use server_fn::middleware::Service;

let method = req.method().clone();
let path = req.uri().path().to_string();
let (req, parts) = generate_request_and_parts(req);
Expand Down Expand Up @@ -510,7 +508,7 @@
/// The difference between calling this and `render_app_to_stream_with_context()` is that this
/// one respects the `SsrMode` on each Route and thus requires `Vec<AxumRouteListing>` for route checking.
/// This is useful if you are using `.leptos_routes_with_handler()`
#[cfg_attr(

Check warning on line 511 in integrations/axum/src/lib.rs

View workflow job for this annotation

GitHub Actions / autofix

very complex type used. Consider factoring parts into `type` definitions
feature = "tracing",
tracing::instrument(level = "trace", fields(error), skip_all)
)]
Expand Down Expand Up @@ -657,7 +655,7 @@
app_fn,
false,
)
}

Check warning on line 658 in integrations/axum/src/lib.rs

View workflow job for this annotation

GitHub Actions / autofix

very complex type used. Consider factoring parts into `type` definitions
/// Returns an Axum [Handler](axum::handler::Handler) that listens for a `GET` request and tries
/// to route it using [leptos_router], serving an HTML stream of your application. It allows you
/// to pass in a context function with additional info to be made available to the app
Expand Down
16 changes: 11 additions & 5 deletions leptos/src/form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ use leptos_dom::helpers::window;
use leptos_server::{ServerAction, ServerMultiAction};
use serde::de::DeserializeOwned;
use server_fn::{
client::Client, codec::PostUrl, request::ClientReq, ServerFn, ServerFnError,
client::Client,
codec::PostUrl,
error::{IntoAppError, ServerFnErrorErr},
request::ClientReq,
ServerFn,
};
use tachys::{
either::Either,
Expand Down Expand Up @@ -123,9 +127,10 @@ where
"Error converting form field into server function \
arguments: {err:?}"
);
value.set(Some(Err(ServerFnError::Serialization(
value.set(Some(Err(ServerFnErrorErr::Serialization(
err.to_string(),
))));
)
.into_app_error())));
version.update(|n| *n += 1);
}
}
Expand Down Expand Up @@ -191,9 +196,10 @@ where
action.dispatch(new_input);
}
Err(err) => {
action.dispatch_sync(Err(ServerFnError::Serialization(
action.dispatch_sync(Err(ServerFnErrorErr::Serialization(
err.to_string(),
)));
)
.into_app_error()));
}
}
};
Expand Down
2 changes: 1 addition & 1 deletion leptos/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ pub mod prelude {
actions::*, computed::*, effect::*, graph::untrack, owner::*,
signal::*, wrappers::read::*,
};
pub use server_fn::{self, ServerFnError};
pub use server_fn::{self, error::ServerFnError};
pub use tachys::{
reactive_graph::{bind::BindAttribute, node_ref::*, Suspend},
view::{
Expand Down
18 changes: 9 additions & 9 deletions leptos_server/src/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use reactive_graph::{
owner::use_context,
traits::DefinedAt,
};
use server_fn::{error::ServerFnErrorSerde, ServerFn, ServerFnError};
use server_fn::{error::FromServerFnError, ServerFn};
use std::{ops::Deref, panic::Location, sync::Arc};

/// An error that can be caused by a server action.
Expand Down Expand Up @@ -42,7 +42,7 @@ where
S: ServerFn + 'static,
S::Output: 'static,
{
inner: ArcAction<S, Result<S::Output, ServerFnError<S::Error>>>,
inner: ArcAction<S, Result<S::Output, S::Error>>,
#[cfg(debug_assertions)]
defined_at: &'static Location<'static>,
}
Expand All @@ -52,13 +52,14 @@ where
S: ServerFn + Clone + Send + Sync + 'static,
S::Output: Send + Sync + 'static,
S::Error: Send + Sync + 'static,
S::Error: FromServerFnError,
{
/// Creates a new [`ArcAction`] that will call the server function `S` when dispatched.
#[track_caller]
pub fn new() -> Self {
let err = use_context::<ServerActionError>().and_then(|error| {
(error.path() == S::PATH)
.then(|| ServerFnError::<S::Error>::de(error.err()))
.then(|| S::Error::de(error.err()))
.map(Err)
});
Self {
Expand All @@ -76,7 +77,7 @@ where
S: ServerFn + 'static,
S::Output: 'static,
{
type Target = ArcAction<S, Result<S::Output, ServerFnError<S::Error>>>;
type Target = ArcAction<S, Result<S::Output, S::Error>>;

fn deref(&self) -> &Self::Target {
&self.inner
Expand Down Expand Up @@ -131,7 +132,7 @@ where
S: ServerFn + 'static,
S::Output: 'static,
{
inner: Action<S, Result<S::Output, ServerFnError<S::Error>>>,
inner: Action<S, Result<S::Output, S::Error>>,
#[cfg(debug_assertions)]
defined_at: &'static Location<'static>,
}
Expand All @@ -146,7 +147,7 @@ where
pub fn new() -> Self {
let err = use_context::<ServerActionError>().and_then(|error| {
(error.path() == S::PATH)
.then(|| ServerFnError::<S::Error>::de(error.err()))
.then(|| S::Error::de(error.err()))
.map(Err)
});
Self {
Expand Down Expand Up @@ -182,15 +183,14 @@ where
S::Output: Send + Sync + 'static,
S::Error: Send + Sync + 'static,
{
type Target = Action<S, Result<S::Output, ServerFnError<S::Error>>>;
type Target = Action<S, Result<S::Output, S::Error>>;

fn deref(&self) -> &Self::Target {
&self.inner
}
}

impl<S> From<ServerAction<S>>
for Action<S, Result<S::Output, ServerFnError<S::Error>>>
impl<S> From<ServerAction<S>> for Action<S, Result<S::Output, S::Error>>
where
S: ServerFn + 'static,
S::Output: 'static,
Expand Down
Loading
Loading