From e2b28a1593fe7acd87b74745c22f35198387f130 Mon Sep 17 00:00:00 2001 From: Ryo Hirayama Date: Fri, 22 Nov 2024 09:47:48 +0900 Subject: [PATCH 01/15] Add FromServerFnError trait to server_fn crate --- Cargo.lock | 1 + leptos_server/src/action.rs | 21 +-- leptos_server/src/multi_action.rs | 12 +- server_fn/Cargo.toml | 1 + server_fn/src/client.rs | 35 ++--- server_fn/src/codec/cbor.rs | 62 ++++----- server_fn/src/codec/json.rs | 124 +++++++++--------- server_fn/src/codec/mod.rs | 64 ++++------ server_fn/src/codec/msgpack.rs | 51 ++++---- server_fn/src/codec/multipart.rs | 20 ++- server_fn/src/codec/postcard.rs | 51 ++++---- server_fn/src/codec/rkyv.rs | 56 ++++---- server_fn/src/codec/serde_lite.rs | 62 +++++---- server_fn/src/codec/stream.rs | 119 ++++++++--------- server_fn/src/codec/url.rs | 66 +++++----- server_fn/src/error.rs | 204 ++++++++++++++++++++++++------ server_fn/src/lib.rs | 25 ++-- server_fn/src/middleware/mod.rs | 39 +++--- server_fn/src/request/actix.rs | 43 +++---- server_fn/src/request/axum.rs | 34 ++--- server_fn/src/request/browser.rs | 53 +++++--- server_fn/src/request/generic.rs | 24 ++-- server_fn/src/request/mod.rs | 44 +++---- server_fn/src/request/reqwest.rs | 36 +++--- server_fn/src/request/spin.rs | 24 ++-- server_fn/src/response/actix.rs | 30 ++--- server_fn/src/response/browser.rs | 39 +++--- server_fn/src/response/generic.rs | 39 ++---- server_fn/src/response/http.rs | 40 ++---- server_fn/src/response/mod.rs | 54 +++----- server_fn/src/response/reqwest.rs | 27 ++-- 31 files changed, 772 insertions(+), 728 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 85a9397945..9617ae6e96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3266,6 +3266,7 @@ version = "0.7.0-rc1" dependencies = [ "actix-web", "axum", + "base64", "bytes", "ciborium", "const_format", diff --git a/leptos_server/src/action.rs b/leptos_server/src/action.rs index 177fb9cff1..cbe81b189d 100644 --- a/leptos_server/src/action.rs +++ b/leptos_server/src/action.rs @@ -3,7 +3,10 @@ use reactive_graph::{ owner::use_context, traits::DefinedAt, }; -use server_fn::{error::ServerFnErrorSerde, ServerFn, ServerFnError}; +use server_fn::{ + error::{FromServerFnError, ServerFnErrorSerde}, + ServerFn, +}; use std::{ops::Deref, panic::Location, sync::Arc}; /// An error that can be caused by a server action. @@ -42,7 +45,7 @@ where S: ServerFn + 'static, S::Output: 'static, { - inner: ArcAction>>, + inner: ArcAction>, #[cfg(debug_assertions)] defined_at: &'static Location<'static>, } @@ -52,13 +55,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::().and_then(|error| { (error.path() == S::PATH) - .then(|| ServerFnError::::de(error.err())) + .then(|| S::Error::de(error.err())) .map(Err) }); Self { @@ -76,7 +80,7 @@ where S: ServerFn + 'static, S::Output: 'static, { - type Target = ArcAction>>; + type Target = ArcAction>; fn deref(&self) -> &Self::Target { &self.inner @@ -131,7 +135,7 @@ where S: ServerFn + 'static, S::Output: 'static, { - inner: Action>>, + inner: Action>, #[cfg(debug_assertions)] defined_at: &'static Location<'static>, } @@ -146,7 +150,7 @@ where pub fn new() -> Self { let err = use_context::().and_then(|error| { (error.path() == S::PATH) - .then(|| ServerFnError::::de(error.err())) + .then(|| S::Error::de(error.err())) .map(Err) }); Self { @@ -182,15 +186,14 @@ where S::Output: Send + Sync + 'static, S::Error: Send + Sync + 'static, { - type Target = Action>>; + type Target = Action>; fn deref(&self) -> &Self::Target { &self.inner } } -impl From> - for Action>> +impl From> for Action> where S: ServerFn + 'static, S::Output: 'static, diff --git a/leptos_server/src/multi_action.rs b/leptos_server/src/multi_action.rs index d62a6a9054..64e7fc2985 100644 --- a/leptos_server/src/multi_action.rs +++ b/leptos_server/src/multi_action.rs @@ -2,7 +2,7 @@ use reactive_graph::{ actions::{ArcMultiAction, MultiAction}, traits::DefinedAt, }; -use server_fn::{ServerFn, ServerFnError}; +use server_fn::ServerFn; use std::{ops::Deref, panic::Location}; /// An [`ArcMultiAction`] that can be used to call a server function. @@ -11,7 +11,7 @@ where S: ServerFn + 'static, S::Output: 'static, { - inner: ArcMultiAction>>, + inner: ArcMultiAction>, #[cfg(debug_assertions)] defined_at: &'static Location<'static>, } @@ -40,7 +40,7 @@ where S: ServerFn + 'static, S::Output: 'static, { - type Target = ArcMultiAction>>; + type Target = ArcMultiAction>; fn deref(&self) -> &Self::Target { &self.inner @@ -95,13 +95,13 @@ where S: ServerFn + 'static, S::Output: 'static, { - inner: MultiAction>>, + inner: MultiAction>, #[cfg(debug_assertions)] defined_at: &'static Location<'static>, } impl From> - for MultiAction>> + for MultiAction> where S: ServerFn + 'static, S::Output: 'static, @@ -152,7 +152,7 @@ where S::Output: 'static, S::Error: 'static, { - type Target = MultiAction>>; + type Target = MultiAction>; fn deref(&self) -> &Self::Target { &self.inner diff --git a/server_fn/Cargo.toml b/server_fn/Cargo.toml index 98e56e3b57..088fe80942 100644 --- a/server_fn/Cargo.toml +++ b/server_fn/Cargo.toml @@ -53,6 +53,7 @@ bytes = "1.8" http-body-util = { version = "0.1.2", optional = true } rkyv = { version = "0.8.8", optional = true } rmp-serde = { version = "1.3.0", optional = true } +base64 = { version = "0.22.1" } # client gloo-net = { version = "0.6.0", optional = true } diff --git a/server_fn/src/client.rs b/server_fn/src/client.rs index 502fd4c4b7..47026d29a8 100644 --- a/server_fn/src/client.rs +++ b/server_fn/src/client.rs @@ -1,4 +1,4 @@ -use crate::{error::ServerFnError, request::ClientReq, response::ClientRes}; +use crate::{request::ClientReq, response::ClientRes}; use std::{future::Future, sync::OnceLock}; static ROOT_URL: OnceLock<&'static str> = OnceLock::new(); @@ -21,16 +21,16 @@ pub fn get_server_url() -> &'static str { /// This trait is implemented for things like a browser `fetch` request or for /// the `reqwest` trait. It should almost never be necessary to implement it /// yourself, unless you’re trying to use an alternative HTTP crate on the client side. -pub trait Client { +pub trait Client { /// The type of a request sent by this client. - type Request: ClientReq + Send; + type Request: ClientReq + Send; /// The type of a response received by this client. - type Response: ClientRes + Send; + type Response: ClientRes + Send; /// Sends the request and receives a response. fn send( req: Self::Request, - ) -> impl Future>> + Send; + ) -> impl Future> + Send; } #[cfg(feature = "browser")] @@ -38,24 +38,23 @@ pub trait Client { pub mod browser { use super::Client; use crate::{ - error::ServerFnError, + error::{FromServerFnError, ServerFnErrorErr}, request::browser::{BrowserRequest, RequestInner}, response::browser::BrowserResponse, }; use send_wrapper::SendWrapper; use std::future::Future; - /// Implements [`Client`] for a `fetch` request in the browser. + /// Implements [`Client`] for a `fetch` request in the browser. pub struct BrowserClient; - impl Client for BrowserClient { + impl Client for BrowserClient { type Request = BrowserRequest; type Response = BrowserResponse; fn send( req: Self::Request, - ) -> impl Future>> - + Send { + ) -> impl Future> + Send { SendWrapper::new(async move { let req = req.0.take(); let RequestInner { @@ -66,7 +65,9 @@ pub mod browser { .send() .await .map(|res| BrowserResponse(SendWrapper::new(res))) - .map_err(|e| ServerFnError::Request(e.to_string())); + .map_err(|e| { + ServerFnErrorErr::Request(e.to_string()).into() + }); // at this point, the future has successfully resolved without being dropped, so we // can prevent the `AbortController` from firing @@ -83,7 +84,10 @@ pub mod browser { /// Implements [`Client`] for a request made by [`reqwest`]. pub mod reqwest { use super::Client; - use crate::{error::ServerFnError, request::reqwest::CLIENT}; + use crate::{ + error::{FromServerFnError, ServerFnErrorErr}, + request::reqwest::CLIENT, + }; use futures::TryFutureExt; use reqwest::{Request, Response}; use std::future::Future; @@ -91,17 +95,16 @@ pub mod reqwest { /// Implements [`Client`] for a request made by [`reqwest`]. pub struct ReqwestClient; - impl Client for ReqwestClient { + impl Client for ReqwestClient { type Request = Request; type Response = Response; fn send( req: Self::Request, - ) -> impl Future>> - + Send { + ) -> impl Future> + Send { CLIENT .execute(req) - .map_err(|e| ServerFnError::Request(e.to_string())) + .map_err(|e| ServerFnErrorErr::Request(e.to_string()).into()) } } } diff --git a/server_fn/src/codec/cbor.rs b/server_fn/src/codec/cbor.rs index a5a91c8117..25d3d2a193 100644 --- a/server_fn/src/codec/cbor.rs +++ b/server_fn/src/codec/cbor.rs @@ -1,6 +1,6 @@ use super::{Encoding, FromReq, FromRes, IntoReq, IntoRes}; use crate::{ - error::ServerFnError, + error::{FromServerFnError, ServerFnErrorErr}, request::{ClientReq, Req}, response::{ClientRes, Res}, }; @@ -16,19 +16,17 @@ impl Encoding for Cbor { const METHOD: Method = Method::POST; } -impl IntoReq for T +impl IntoReq for T where - Request: ClientReq, + Request: ClientReq, T: Serialize + Send, + E: FromServerFnError, { - fn into_req( - self, - path: &str, - accepts: &str, - ) -> Result> { + fn into_req(self, path: &str, accepts: &str) -> Result { let mut buffer: Vec = Vec::new(); - ciborium::ser::into_writer(&self, &mut buffer) - .map_err(|e| ServerFnError::Serialization(e.to_string()))?; + ciborium::ser::into_writer(&self, &mut buffer).map_err(|e| { + E::from(ServerFnErrorErr::Serialization(e.to_string())) + })?; Request::try_new_post_bytes( path, accepts, @@ -38,40 +36,44 @@ where } } -impl FromReq for T +impl FromReq for T where - Request: Req + Send + 'static, + Request: Req + Send + 'static, T: DeserializeOwned, + E: FromServerFnError, { - async fn from_req(req: Request) -> Result> { + async fn from_req(req: Request) -> Result { let body_bytes = req.try_into_bytes().await?; ciborium::de::from_reader(body_bytes.as_ref()) - .map_err(|e| ServerFnError::Args(e.to_string())) + .map_err(|e| E::from(ServerFnErrorErr::Args(e.to_string()))) } } -impl IntoRes for T +impl IntoRes for T where - Response: Res, + Response: Res, T: Serialize + Send, + E: FromServerFnError, { - async fn into_res(self) -> Result> { + async fn into_res(self) -> Result { let mut buffer: Vec = Vec::new(); - ciborium::ser::into_writer(&self, &mut buffer) - .map_err(|e| ServerFnError::Serialization(e.to_string()))?; + ciborium::ser::into_writer(&self, &mut buffer).map_err(|e| { + E::from(ServerFnErrorErr::Serialization(e.to_string())) + })?; Response::try_from_bytes(Cbor::CONTENT_TYPE, Bytes::from(buffer)) } } -impl FromRes for T +impl FromRes for T where - Response: ClientRes + Send, + Response: ClientRes + Send, T: DeserializeOwned + Send, + E: FromServerFnError, { - async fn from_res(res: Response) -> Result> { + async fn from_res(res: Response) -> Result { let data = res.try_into_bytes().await?; ciborium::de::from_reader(data.as_ref()) - .map_err(|e| ServerFnError::Args(e.to_string())) + .map_err(|e| E::from(ServerFnErrorErr::Args(e.to_string()))) } } @@ -114,20 +116,20 @@ where ::Data: Send , ::Data: Send , { - async fn from_req(req: http::Request) -> Result> { + async fn from_req(req: http::Request) -> Result> { let (_parts, body) = req.into_parts(); let body_bytes = body .collect() .await .map(|c| c.to_bytes()) - .map_err(|e| ServerFnError::Deserialization(e.to_string()))?; + .map_err(|e| ServerFnErrorErr::Deserialization(e.to_string()).into())?; let data = ciborium::de::from_reader(body_bytes.as_ref()) - .map_err(|e| ServerFnError::Args(e.to_string()))?; + .map_err(|e| ServerFnErrorErr::Args(e.to_string()).into())?; Ok(data) } - async fn into_req(self) -> Result, ServerFnError> { + async fn into_req(self) -> Result, ServerFnError> { let mut buffer: Vec = Vec::new(); ciborium::ser::into_writer(&self, &mut buffer)?; let req = http::Request::builder() @@ -139,17 +141,17 @@ where .body(Body::from(buffer))?; Ok(req) } - async fn from_res(res: http::Response) -> Result> { + async fn from_res(res: http::Response) -> Result> { let (_parts, body) = res.into_parts(); let body_bytes = body .collect() .await .map(|c| c.to_bytes()) - .map_err(|e| ServerFnError::Deserialization(e.to_string()))?; + .map_err(|e| ServerFnErrorErr::Deserialization(e.to_string()).into())?; ciborium::de::from_reader(body_bytes.as_ref()) - .map_err(|e| ServerFnError::Args(e.to_string())) + .map_err(|e| ServerFnErrorErr::Args(e.to_string()).into()) } async fn into_res(self) -> http::Response { diff --git a/server_fn/src/codec/json.rs b/server_fn/src/codec/json.rs index d4324d61d6..2df9c8c185 100644 --- a/server_fn/src/codec/json.rs +++ b/server_fn/src/codec/json.rs @@ -1,6 +1,6 @@ use super::{Encoding, FromReq, FromRes, Streaming}; use crate::{ - error::{NoCustomError, ServerFnError}, + error::{FromServerFnError, ServerFnErrorErr}, request::{ClientReq, Req}, response::{ClientRes, Res}, IntoReq, IntoRes, @@ -18,55 +18,58 @@ impl Encoding for Json { const METHOD: Method = Method::POST; } -impl IntoReq for T +impl IntoReq for T where - Request: ClientReq, + Request: ClientReq, T: Serialize + Send, + E: FromServerFnError, { - fn into_req( - self, - path: &str, - accepts: &str, - ) -> Result> { - let data = serde_json::to_string(&self) - .map_err(|e| ServerFnError::Serialization(e.to_string()))?; + fn into_req(self, path: &str, accepts: &str) -> Result { + let data = serde_json::to_string(&self).map_err(|e| { + E::from(ServerFnErrorErr::Serialization(e.to_string())) + })?; Request::try_new_post(path, accepts, Json::CONTENT_TYPE, data) } } -impl FromReq for T +impl FromReq for T where - Request: Req + Send + 'static, + Request: Req + Send + 'static, T: DeserializeOwned, + E: FromServerFnError, { - async fn from_req(req: Request) -> Result> { + async fn from_req(req: Request) -> Result { let string_data = req.try_into_string().await?; serde_json::from_str::(&string_data) - .map_err(|e| ServerFnError::Args(e.to_string())) + .map_err(|e| E::from(ServerFnErrorErr::Args(e.to_string()))) } } -impl IntoRes for T +impl IntoRes for T where - Response: Res, + Response: Res, T: Serialize + Send, + E: FromServerFnError, { - async fn into_res(self) -> Result> { - let data = serde_json::to_string(&self) - .map_err(|e| ServerFnError::Serialization(e.to_string()))?; + async fn into_res(self) -> Result { + let data = serde_json::to_string(&self).map_err(|e| { + E::from(ServerFnErrorErr::Serialization(e.to_string())) + })?; Response::try_from_string(Json::CONTENT_TYPE, data) } } -impl FromRes for T +impl FromRes for T where - Response: ClientRes + Send, + Response: ClientRes + Send, T: DeserializeOwned + Send, + E: FromServerFnError, { - async fn from_res(res: Response) -> Result> { + async fn from_res(res: Response) -> Result { let data = res.try_into_string().await?; - serde_json::from_str(&data) - .map_err(|e| ServerFnError::Deserialization(e.to_string())) + serde_json::from_str(&data).map_err(|e| { + E::from(ServerFnErrorErr::Deserialization(e.to_string())) + }) } } @@ -102,35 +105,31 @@ impl Encoding for StreamingJson { /// end before the output will begin. /// /// Streaming requests are only allowed over HTTP2 or HTTP3. -pub struct JsonStream( - Pin>> + Send>>, -); +pub struct JsonStream(Pin> + Send>>); -impl std::fmt::Debug for JsonStream { +impl std::fmt::Debug for JsonStream { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_tuple("JsonStream").finish() } } -impl JsonStream { +impl JsonStream { /// Creates a new `ByteStream` from the given stream. pub fn new( - value: impl Stream> + Send + 'static, + value: impl Stream> + Send + 'static, ) -> Self { Self(Box::pin(value.map(|value| value.map(Into::into)))) } } -impl JsonStream { +impl JsonStream { /// Consumes the wrapper, returning a stream of text. - pub fn into_inner( - self, - ) -> impl Stream>> + Send { + pub fn into_inner(self) -> impl Stream> + Send { self.0 } } -impl From for JsonStream +impl From for JsonStream where S: Stream + Send + 'static, { @@ -139,18 +138,15 @@ where } } -impl IntoReq for S +impl IntoReq for S where - Request: ClientReq, + Request: ClientReq, S: Stream + Send + 'static, T: Serialize + 'static, + E: FromServerFnError + Serialize, { - fn into_req( - self, - path: &str, - accepts: &str, - ) -> Result> { - let data: JsonStream = self.into(); + fn into_req(self, path: &str, accepts: &str) -> Result { + let data: JsonStream = self.into(); Request::try_new_streaming( path, accepts, @@ -164,56 +160,58 @@ where } } -impl FromReq for S +impl FromReq for S where - Request: Req + Send + 'static, + Request: Req + Send + 'static, // The additional `Stream` bound is never used, but it is required to avoid an error where `T` is unconstrained - S: Stream + From> + Send + 'static, + S: Stream + From> + Send + 'static, T: DeserializeOwned + 'static, + E: FromServerFnError, { - async fn from_req(req: Request) -> Result> { + async fn from_req(req: Request) -> Result { let data = req.try_into_stream()?; let s = JsonStream::new(data.map(|chunk| { chunk.and_then(|bytes| { - serde_json::from_slice(bytes.as_ref()) - .map_err(|e| ServerFnError::Deserialization(e.to_string())) + serde_json::from_slice(bytes.as_ref()).map_err(|e| { + E::from(ServerFnErrorErr::Deserialization(e.to_string())) + }) }) })); Ok(s.into()) } } -impl IntoRes - for JsonStream +impl IntoRes for JsonStream where - Response: Res, - CustErr: 'static, + Response: Res, T: Serialize + 'static, + E: FromServerFnError, { - async fn into_res(self) -> Result> { + async fn into_res(self) -> Result { Response::try_from_stream( Streaming::CONTENT_TYPE, self.into_inner().map(|value| { - serde_json::to_vec(&value?) - .map(Bytes::from) - .map_err(|e| ServerFnError::Serialization(e.to_string())) + serde_json::to_vec(&value?).map(Bytes::from).map_err(|e| { + ServerFnErrorErr::Serialization(e.to_string()).into() + }) }), ) } } -impl FromRes - for JsonStream +impl FromRes for JsonStream where - Response: ClientRes + Send, + Response: ClientRes + Send, T: DeserializeOwned, + E: FromServerFnError, { - async fn from_res(res: Response) -> Result> { + async fn from_res(res: Response) -> Result { let stream = res.try_into_stream()?; Ok(JsonStream::new(stream.map(|chunk| { chunk.and_then(|bytes| { - serde_json::from_slice(bytes.as_ref()) - .map_err(|e| ServerFnError::Deserialization(e.to_string())) + serde_json::from_slice(bytes.as_ref()).map_err(|e| { + ServerFnErrorErr::Deserialization(e.to_string()).into() + }) }) }))) } diff --git a/server_fn/src/codec/mod.rs b/server_fn/src/codec/mod.rs index fc5bb37846..33676cfb7f 100644 --- a/server_fn/src/codec/mod.rs +++ b/server_fn/src/codec/mod.rs @@ -55,7 +55,6 @@ mod postcard; pub use postcard::*; mod stream; -use crate::error::ServerFnError; use futures::Future; use http::Method; pub use stream::*; @@ -71,31 +70,27 @@ pub use stream::*; /// For example, here’s the implementation for [`Json`]. /// /// ```rust,ignore -/// impl IntoReq for T +/// impl IntoReq for T /// where -/// Request: ClientReq, +/// Request: ClientReq, /// T: Serialize + Send, /// { /// fn into_req( /// self, /// path: &str, /// accepts: &str, -/// ) -> Result> { +/// ) -> Result { /// // try to serialize the data /// let data = serde_json::to_string(&self) -/// .map_err(|e| ServerFnError::Serialization(e.to_string()))?; +/// .map_err(|e| ServerFnErrorErr::Serialization(e.to_string()).into())?; /// // and use it as the body of a POST request /// Request::try_new_post(path, accepts, Json::CONTENT_TYPE, data) /// } /// } /// ``` -pub trait IntoReq { +pub trait IntoReq { /// Attempts to serialize the arguments into an HTTP request. - fn into_req( - self, - path: &str, - accepts: &str, - ) -> Result>; + fn into_req(self, path: &str, accepts: &str) -> Result; } /// Deserializes an HTTP request into the data type, on the server. @@ -109,32 +104,31 @@ pub trait IntoReq { /// For example, here’s the implementation for [`Json`]. /// /// ```rust,ignore -/// impl FromReq for T +/// impl FromReq for T /// where /// // require the Request implement `Req` -/// Request: Req + Send + 'static, +/// Request: Req + Send + 'static, /// // require that the type can be deserialized with `serde` /// T: DeserializeOwned, +/// E: FromServerFnError, /// { /// async fn from_req( /// req: Request, -/// ) -> Result> { +/// ) -> Result { /// // try to convert the body of the request into a `String` /// let string_data = req.try_into_string().await?; /// // deserialize the data -/// serde_json::from_str::(&string_data) -/// .map_err(|e| ServerFnError::Args(e.to_string())) +/// serde_json::from_str(&string_data) +/// .map_err(|e| ServerFnErrorErr::Args(e.to_string()).into()) /// } /// } /// ``` -pub trait FromReq +pub trait FromReq where Self: Sized, { /// Attempts to deserialize the arguments from a request. - fn from_req( - req: Request, - ) -> impl Future>> + Send; + fn from_req(req: Request) -> impl Future> + Send; } /// Serializes the data type into an HTTP response. @@ -148,25 +142,24 @@ where /// For example, here’s the implementation for [`Json`]. /// /// ```rust,ignore -/// impl IntoRes for T +/// impl IntoRes for T /// where -/// Response: Res, +/// Response: Res, /// T: Serialize + Send, +/// E: FromServerFnError, /// { -/// async fn into_res(self) -> Result> { +/// async fn into_res(self) -> Result { /// // try to serialize the data /// let data = serde_json::to_string(&self) -/// .map_err(|e| ServerFnError::Serialization(e.to_string()))?; +/// .map_err(|e| ServerFnErrorErr::Serialization(e.to_string()).into())?; /// // and use it as the body of a response /// Response::try_from_string(Json::CONTENT_TYPE, data) /// } /// } /// ``` -pub trait IntoRes { +pub trait IntoRes { /// Attempts to serialize the output into an HTTP response. - fn into_res( - self, - ) -> impl Future>> + Send; + fn into_res(self) -> impl Future> + Send; } /// Deserializes the data type from an HTTP response. @@ -181,30 +174,29 @@ pub trait IntoRes { /// For example, here’s the implementation for [`Json`]. /// /// ```rust,ignore -/// impl FromRes for T +/// impl FromRes for T /// where -/// Response: ClientRes + Send, +/// Response: ClientRes + Send, /// T: DeserializeOwned + Send, +/// E: FromServerFnError, /// { /// async fn from_res( /// res: Response, -/// ) -> Result> { +/// ) -> Result { /// // extracts the request body /// let data = res.try_into_string().await?; /// // and tries to deserialize it as JSON /// serde_json::from_str(&data) -/// .map_err(|e| ServerFnError::Deserialization(e.to_string())) +/// .map_err(|e| ServerFnErrorErr::Deserialization(e.to_string()).into()) /// } /// } /// ``` -pub trait FromRes +pub trait FromRes where Self: Sized, { /// Attempts to deserialize the outputs from a response. - fn from_res( - res: Response, - ) -> impl Future>> + Send; + fn from_res(res: Response) -> impl Future> + Send; } /// Defines a particular encoding format, which can be used for serializing or deserializing data. diff --git a/server_fn/src/codec/msgpack.rs b/server_fn/src/codec/msgpack.rs index c06789e1a6..5ba9c0d253 100644 --- a/server_fn/src/codec/msgpack.rs +++ b/server_fn/src/codec/msgpack.rs @@ -1,6 +1,6 @@ use super::{Encoding, FromReq, FromRes, IntoReq, IntoRes}; use crate::{ - error::ServerFnError, + error::{FromServerFnError, ServerFnErrorErr}, request::{ClientReq, Req}, response::{ClientRes, Res}, }; @@ -16,18 +16,16 @@ impl Encoding for MsgPack { const METHOD: Method = Method::POST; } -impl IntoReq for T +impl IntoReq for T where - Request: ClientReq, + Request: ClientReq, T: Serialize, + E: FromServerFnError, { - fn into_req( - self, - path: &str, - accepts: &str, - ) -> Result> { - let data = rmp_serde::to_vec(&self) - .map_err(|e| ServerFnError::Serialization(e.to_string()))?; + fn into_req(self, path: &str, accepts: &str) -> Result { + let data = rmp_serde::to_vec(&self).map_err(|e| { + E::from(ServerFnErrorErr::Serialization(e.to_string())) + })?; Request::try_new_post_bytes( path, MsgPack::CONTENT_TYPE, @@ -37,38 +35,43 @@ where } } -impl FromReq for T +impl FromReq for T where - Request: Req + Send, + Request: Req + Send, T: DeserializeOwned, + E: FromServerFnError, { - async fn from_req(req: Request) -> Result> { + async fn from_req(req: Request) -> Result { let data = req.try_into_bytes().await?; rmp_serde::from_slice::(&data) - .map_err(|e| ServerFnError::Args(e.to_string())) + .map_err(|e| ServerFnErrorErr::Args(e.to_string()).into()) } } -impl IntoRes for T +impl IntoRes for T where - Response: Res, + Response: Res, T: Serialize + Send, + E: FromServerFnError, { - async fn into_res(self) -> Result> { - let data = rmp_serde::to_vec(&self) - .map_err(|e| ServerFnError::Serialization(e.to_string()))?; + async fn into_res(self) -> Result { + let data = rmp_serde::to_vec(&self).map_err(|e| { + E::from(ServerFnErrorErr::Serialization(e.to_string())) + })?; Response::try_from_bytes(MsgPack::CONTENT_TYPE, Bytes::from(data)) } } -impl FromRes for T +impl FromRes for T where - Response: ClientRes + Send, + Response: ClientRes + Send, T: DeserializeOwned, + E: FromServerFnError, { - async fn from_res(res: Response) -> Result> { + async fn from_res(res: Response) -> Result { let data = res.try_into_bytes().await?; - rmp_serde::from_slice(&data) - .map_err(|e| ServerFnError::Deserialization(e.to_string())) + rmp_serde::from_slice(&data).map_err(|e| { + E::from(ServerFnErrorErr::Deserialization(e.to_string())) + }) } } diff --git a/server_fn/src/codec/multipart.rs b/server_fn/src/codec/multipart.rs index 998e822260..f1ddb98117 100644 --- a/server_fn/src/codec/multipart.rs +++ b/server_fn/src/codec/multipart.rs @@ -1,6 +1,6 @@ use super::{Encoding, FromReq}; use crate::{ - error::ServerFnError, + error::FromServerFnError, request::{browser::BrowserFormData, ClientReq, Req}, IntoReq, }; @@ -56,16 +56,12 @@ impl From for MultipartData { } } -impl IntoReq for T +impl IntoReq for T where - Request: ClientReq, + Request: ClientReq, T: Into, { - fn into_req( - self, - path: &str, - accepts: &str, - ) -> Result> { + fn into_req(self, path: &str, accepts: &str) -> Result { let multi = self.into(); Request::try_new_multipart( path, @@ -75,13 +71,13 @@ where } } -impl FromReq for T +impl FromReq for T where - Request: Req + Send + 'static, + Request: Req + Send + 'static, T: From, - CustErr: 'static, + E: FromServerFnError, { - async fn from_req(req: Request) -> Result> { + async fn from_req(req: Request) -> Result { let boundary = req .to_content_type() .and_then(|ct| multer::parse_boundary(ct).ok()) diff --git a/server_fn/src/codec/postcard.rs b/server_fn/src/codec/postcard.rs index b78dae63b2..65f80cfcb0 100644 --- a/server_fn/src/codec/postcard.rs +++ b/server_fn/src/codec/postcard.rs @@ -1,6 +1,6 @@ use super::{Encoding, FromReq, FromRes, IntoReq, IntoRes}; use crate::{ - error::ServerFnError, + error::{FromServerFnError, ServerFnErrorErr}, request::{ClientReq, Req}, response::{ClientRes, Res}, }; @@ -16,18 +16,16 @@ impl Encoding for Postcard { const METHOD: Method = Method::POST; } -impl IntoReq for T +impl IntoReq for T where - Request: ClientReq, + Request: ClientReq, T: Serialize, + E: FromServerFnError, { - fn into_req( - self, - path: &str, - accepts: &str, - ) -> Result> { - let data = postcard::to_allocvec(&self) - .map_err(|e| ServerFnError::Serialization(e.to_string()))?; + fn into_req(self, path: &str, accepts: &str) -> Result { + let data = postcard::to_allocvec(&self).map_err(|e| { + E::from(ServerFnErrorErr::Serialization(e.to_string())) + })?; Request::try_new_post_bytes( path, Postcard::CONTENT_TYPE, @@ -37,38 +35,43 @@ where } } -impl FromReq for T +impl FromReq for T where - Request: Req + Send, + Request: Req + Send, T: DeserializeOwned, + E: FromServerFnError, { - async fn from_req(req: Request) -> Result> { + async fn from_req(req: Request) -> Result { let data = req.try_into_bytes().await?; postcard::from_bytes::(&data) - .map_err(|e| ServerFnError::Args(e.to_string())) + .map_err(|e| E::from(ServerFnErrorErr::Args(e.to_string()))) } } -impl IntoRes for T +impl IntoRes for T where - Response: Res, + Response: Res, T: Serialize + Send, + E: FromServerFnError, { - async fn into_res(self) -> Result> { - let data = postcard::to_allocvec(&self) - .map_err(|e| ServerFnError::Serialization(e.to_string()))?; + async fn into_res(self) -> Result { + let data = postcard::to_allocvec(&self).map_err(|e| { + E::from(ServerFnErrorErr::Serialization(e.to_string())) + })?; Response::try_from_bytes(Postcard::CONTENT_TYPE, Bytes::from(data)) } } -impl FromRes for T +impl FromRes for T where - Response: ClientRes + Send, + Response: ClientRes + Send, T: DeserializeOwned, + E: FromServerFnError, { - async fn from_res(res: Response) -> Result> { + async fn from_res(res: Response) -> Result { let data = res.try_into_bytes().await?; - postcard::from_bytes(&data) - .map_err(|e| ServerFnError::Deserialization(e.to_string())) + postcard::from_bytes(&data).map_err(|e| { + E::from(ServerFnErrorErr::Deserialization(e.to_string())) + }) } } diff --git a/server_fn/src/codec/rkyv.rs b/server_fn/src/codec/rkyv.rs index 9aed0ff83a..86643b3709 100644 --- a/server_fn/src/codec/rkyv.rs +++ b/server_fn/src/codec/rkyv.rs @@ -1,6 +1,6 @@ use super::{Encoding, FromReq, FromRes, IntoReq, IntoRes}; use crate::{ - error::ServerFnError, + error::{FromServerFnError, ServerFnErrorErr}, request::{ClientReq, Req}, response::{ClientRes, Res}, }; @@ -29,39 +29,41 @@ impl Encoding for Rkyv { const METHOD: Method = Method::POST; } -impl IntoReq for T +impl IntoReq for T where - Request: ClientReq, + Request: ClientReq, T: Archive + for<'a> Serialize>, T::Archived: Deserialize + for<'a> CheckBytes>, + E: FromServerFnError, { - fn into_req( - self, - path: &str, - accepts: &str, - ) -> Result> { - let encoded = rkyv::to_bytes::(&self) - .map_err(|e| ServerFnError::Serialization(e.to_string()))?; + fn into_req(self, path: &str, accepts: &str) -> Result { + let encoded = rkyv::to_bytes::(&self).map_err(|e| { + E::from(ServerFnErrorErr::Serialization(e.to_string())) + })?; let bytes = Bytes::copy_from_slice(encoded.as_ref()); Request::try_new_post_bytes(path, accepts, Rkyv::CONTENT_TYPE, bytes) } } -impl FromReq for T +impl FromReq for T where - Request: Req + Send + 'static, + Request: Req + Send + 'static, T: Archive + for<'a> Serialize>, T::Archived: Deserialize + for<'a> CheckBytes>, + E: FromServerFnError, { - async fn from_req(req: Request) -> Result> { + async fn from_req(req: Request) -> Result { let mut aligned = AlignedVec::<1024>::new(); let mut body_stream = Box::pin(req.try_into_stream()?); while let Some(chunk) = body_stream.next().await { match chunk { Err(e) => { - return Err(ServerFnError::Deserialization(e.to_string())) + return Err(ServerFnErrorErr::Deserialization( + e.to_string(), + ) + .into()) } Ok(bytes) => { for byte in bytes { @@ -71,36 +73,40 @@ where } } rkyv::from_bytes::(aligned.as_ref()) - .map_err(|e| ServerFnError::Args(e.to_string())) + .map_err(|e| ServerFnErrorErr::Args(e.to_string()).into()) } } -impl IntoRes for T +impl IntoRes for T where - Response: Res, + Response: Res, T: Send, T: Archive + for<'a> Serialize>, T::Archived: Deserialize + for<'a> CheckBytes>, + E: FromServerFnError, { - async fn into_res(self) -> Result> { - let encoded = rkyv::to_bytes::(&self) - .map_err(|e| ServerFnError::Serialization(format!("{e:?}")))?; + async fn into_res(self) -> Result { + let encoded = rkyv::to_bytes::(&self).map_err(|e| { + E::from(ServerFnErrorErr::Serialization(format!("{e:?}"))) + })?; let bytes = Bytes::copy_from_slice(encoded.as_ref()); Response::try_from_bytes(Rkyv::CONTENT_TYPE, bytes) } } -impl FromRes for T +impl FromRes for T where - Response: ClientRes + Send, + Response: ClientRes + Send, T: Archive + for<'a> Serialize>, T::Archived: Deserialize + for<'a> CheckBytes>, + E: FromServerFnError, { - async fn from_res(res: Response) -> Result> { + async fn from_res(res: Response) -> Result { let data = res.try_into_bytes().await?; - rkyv::from_bytes::(&data) - .map_err(|e| ServerFnError::Deserialization(e.to_string())) + rkyv::from_bytes::(&data).map_err(|e| { + ServerFnErrorErr::Deserialization(e.to_string()).into() + }) } } diff --git a/server_fn/src/codec/serde_lite.rs b/server_fn/src/codec/serde_lite.rs index b71b9390fe..319d698a81 100644 --- a/server_fn/src/codec/serde_lite.rs +++ b/server_fn/src/codec/serde_lite.rs @@ -1,6 +1,6 @@ use super::{Encoding, FromReq, FromRes}; use crate::{ - error::ServerFnError, + error::{FromServerFnError, ServerFnErrorErr}, request::{ClientReq, Req}, response::{ClientRes, Res}, IntoReq, IntoRes, @@ -15,68 +15,64 @@ impl Encoding for SerdeLite { const METHOD: Method = Method::POST; } -impl IntoReq for T +impl IntoReq for T where - Request: ClientReq, + Request: ClientReq, T: Serialize + Send, + E: FromServerFnError, { - fn into_req( - self, - path: &str, - accepts: &str, - ) -> Result> { - let data = serde_json::to_string( - &self - .serialize() - .map_err(|e| ServerFnError::Serialization(e.to_string()))?, - ) - .map_err(|e| ServerFnError::Serialization(e.to_string()))?; + fn into_req(self, path: &str, accepts: &str) -> Result { + let data = serde_json::to_string(&self.serialize().map_err(|e| { + E::from(ServerFnErrorErr::Serialization(e.to_string())) + })?) + .map_err(|e| E::from(ServerFnErrorErr::Serialization(e.to_string())))?; Request::try_new_post(path, accepts, SerdeLite::CONTENT_TYPE, data) } } -impl FromReq for T +impl FromReq for T where - Request: Req + Send + 'static, + Request: Req + Send + 'static, T: Deserialize, + E: FromServerFnError, { - async fn from_req(req: Request) -> Result> { + async fn from_req(req: Request) -> Result { let string_data = req.try_into_string().await?; Self::deserialize( &serde_json::from_str(&string_data) - .map_err(|e| ServerFnError::Args(e.to_string()))?, + .map_err(|e| E::from(ServerFnErrorErr::Args(e.to_string())))?, ) - .map_err(|e| ServerFnError::Args(e.to_string())) + .map_err(|e| E::from(ServerFnErrorErr::Args(e.to_string()))) } } -impl IntoRes for T +impl IntoRes for T where - Response: Res, + Response: Res, T: Serialize + Send, + E: FromServerFnError, { - async fn into_res(self) -> Result> { - let data = serde_json::to_string( - &self - .serialize() - .map_err(|e| ServerFnError::Serialization(e.to_string()))?, - ) - .map_err(|e| ServerFnError::Serialization(e.to_string()))?; + async fn into_res(self) -> Result { + let data = serde_json::to_string(&self.serialize().map_err(|e| { + E::from(ServerFnErrorErr::Serialization(e.to_string())) + })?) + .map_err(|e| E::from(ServerFnErrorErr::Serialization(e.to_string())))?; Response::try_from_string(SerdeLite::CONTENT_TYPE, data) } } -impl FromRes for T +impl FromRes for T where - Response: ClientRes + Send, + Response: ClientRes + Send, T: Deserialize + Send, + E: FromServerFnError, { - async fn from_res(res: Response) -> Result> { + async fn from_res(res: Response) -> Result { let data = res.try_into_string().await?; Self::deserialize( &serde_json::from_str(&data) - .map_err(|e| ServerFnError::Args(e.to_string()))?, + .map_err(|e| E::from(ServerFnErrorErr::Args(e.to_string())))?, ) - .map_err(|e| ServerFnError::Deserialization(e.to_string())) + .map_err(|e| E::from(ServerFnErrorErr::Deserialization(e.to_string()))) } } diff --git a/server_fn/src/codec/stream.rs b/server_fn/src/codec/stream.rs index 15e2765363..95958f968a 100644 --- a/server_fn/src/codec/stream.rs +++ b/server_fn/src/codec/stream.rs @@ -1,6 +1,6 @@ use super::{Encoding, FromReq, FromRes, IntoReq}; use crate::{ - error::{NoCustomError, ServerFnError}, + error::{FromServerFnError, ServerFnErrorErr}, request::{ClientReq, Req}, response::{ClientRes, Res}, IntoRes, @@ -29,26 +29,22 @@ impl Encoding for Streaming { const METHOD: Method = Method::POST; } -impl IntoReq for T +impl IntoReq for T where - Request: ClientReq, + Request: ClientReq, T: Stream + Send + Sync + 'static, { - fn into_req( - self, - path: &str, - accepts: &str, - ) -> Result> { + fn into_req(self, path: &str, accepts: &str) -> Result { Request::try_new_streaming(path, accepts, Streaming::CONTENT_TYPE, self) } } -impl FromReq for T +impl FromReq for T where - Request: Req + Send + 'static, - T: From + 'static, + Request: Req + Send + 'static, + T: From> + 'static, { - async fn from_req(req: Request) -> Result> { + async fn from_req(req: Request) -> Result { let data = req.try_into_stream()?; let s = ByteStream::new(data); Ok(s.into()) @@ -67,29 +63,25 @@ where /// end before the output will begin. /// /// Streaming requests are only allowed over HTTP2 or HTTP3. -pub struct ByteStream( - Pin>> + Send>>, -); +pub struct ByteStream(Pin> + Send>>); -impl ByteStream { +impl ByteStream { /// Consumes the wrapper, returning a stream of bytes. - pub fn into_inner( - self, - ) -> impl Stream>> + Send { + pub fn into_inner(self) -> impl Stream> + Send { self.0 } } -impl Debug for ByteStream { +impl Debug for ByteStream { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_tuple("ByteStream").finish() } } -impl ByteStream { +impl ByteStream { /// Creates a new `ByteStream` from the given stream. pub fn new( - value: impl Stream> + Send + 'static, + value: impl Stream> + Send + 'static, ) -> Self where T: Into, @@ -98,7 +90,7 @@ impl ByteStream { } } -impl From for ByteStream +impl From for ByteStream where S: Stream + Send + 'static, T: Into, @@ -108,22 +100,21 @@ where } } -impl IntoRes - for ByteStream +impl IntoRes for ByteStream where - Response: Res, - CustErr: 'static, + Response: Res, + E: 'static, { - async fn into_res(self) -> Result> { + async fn into_res(self) -> Result { Response::try_from_stream(Streaming::CONTENT_TYPE, self.into_inner()) } } -impl FromRes for ByteStream +impl FromRes for ByteStream where - Response: ClientRes + Send, + Response: ClientRes + Send, { - async fn from_res(res: Response) -> Result> { + async fn from_res(res: Response) -> Result { let stream = res.try_into_stream()?; Ok(ByteStream(Box::pin(stream))) } @@ -160,35 +151,31 @@ impl Encoding for StreamingText { /// end before the output will begin. /// /// Streaming requests are only allowed over HTTP2 or HTTP3. -pub struct TextStream( - Pin>> + Send>>, -); +pub struct TextStream(Pin> + Send>>); -impl Debug for TextStream { +impl Debug for TextStream { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_tuple("TextStream").finish() } } -impl TextStream { +impl TextStream { /// Creates a new `ByteStream` from the given stream. pub fn new( - value: impl Stream> + Send + 'static, + value: impl Stream> + Send + 'static, ) -> Self { Self(Box::pin(value.map(|value| value.map(Into::into)))) } } -impl TextStream { +impl TextStream { /// Consumes the wrapper, returning a stream of text. - pub fn into_inner( - self, - ) -> impl Stream>> + Send { + pub fn into_inner(self) -> impl Stream> + Send { self.0 } } -impl From for TextStream +impl From for TextStream where S: Stream + Send + 'static, T: Into, @@ -198,16 +185,13 @@ where } } -impl IntoReq for T +impl IntoReq for T where - Request: ClientReq, - T: Into, + Request: ClientReq, + T: Into>, + E: 'static, { - fn into_req( - self, - path: &str, - accepts: &str, - ) -> Result> { + fn into_req(self, path: &str, accepts: &str) -> Result { let data = self.into(); Request::try_new_streaming( path, @@ -218,30 +202,31 @@ where } } -impl FromReq for T +impl FromReq for T where - Request: Req + Send + 'static, - T: From + 'static, + Request: Req + Send + 'static, + T: From> + 'static, + E: FromServerFnError, { - async fn from_req(req: Request) -> Result> { + async fn from_req(req: Request) -> Result { let data = req.try_into_stream()?; let s = TextStream::new(data.map(|chunk| { chunk.and_then(|bytes| { - String::from_utf8(bytes.to_vec()) - .map_err(|e| ServerFnError::Deserialization(e.to_string())) + String::from_utf8(bytes.to_vec()).map_err(|e| { + ServerFnErrorErr::Deserialization(e.to_string()).into() + }) }) })); Ok(s.into()) } } -impl IntoRes - for TextStream +impl IntoRes for TextStream where - Response: Res, - CustErr: 'static, + Response: Res, + E: 'static, { - async fn into_res(self) -> Result> { + async fn into_res(self) -> Result { Response::try_from_stream( Streaming::CONTENT_TYPE, self.into_inner().map(|stream| stream.map(Into::into)), @@ -249,16 +234,18 @@ where } } -impl FromRes for TextStream +impl FromRes for TextStream where - Response: ClientRes + Send, + Response: ClientRes + Send, + E: FromServerFnError, { - async fn from_res(res: Response) -> Result> { + async fn from_res(res: Response) -> Result { let stream = res.try_into_stream()?; Ok(TextStream(Box::pin(stream.map(|chunk| { chunk.and_then(|bytes| { - String::from_utf8(bytes.into()) - .map_err(|e| ServerFnError::Deserialization(e.to_string())) + String::from_utf8(bytes.into()).map_err(|e| { + ServerFnErrorErr::Deserialization(e.to_string()).into() + }) }) })))) } diff --git a/server_fn/src/codec/url.rs b/server_fn/src/codec/url.rs index 38e7894649..79eca2d7d5 100644 --- a/server_fn/src/codec/url.rs +++ b/server_fn/src/codec/url.rs @@ -1,6 +1,6 @@ use super::{Encoding, FromReq, IntoReq}; use crate::{ - error::ServerFnError, + error::{FromServerFnError, ServerFnErrorErr}, request::{ClientReq, Req}, }; use http::Method; @@ -17,32 +17,31 @@ impl Encoding for GetUrl { const METHOD: Method = Method::GET; } -impl IntoReq for T +impl IntoReq for T where - Request: ClientReq, + Request: ClientReq, T: Serialize + Send, + E: FromServerFnError, { - fn into_req( - self, - path: &str, - accepts: &str, - ) -> Result> { - let data = serde_qs::to_string(&self) - .map_err(|e| ServerFnError::Serialization(e.to_string()))?; + fn into_req(self, path: &str, accepts: &str) -> Result { + let data = serde_qs::to_string(&self).map_err(|e| { + E::from(ServerFnErrorErr::Serialization(e.to_string())) + })?; Request::try_new_get(path, accepts, GetUrl::CONTENT_TYPE, &data) } } -impl FromReq for T +impl FromReq for T where - Request: Req + Send + 'static, + Request: Req + Send + 'static, T: DeserializeOwned, + E: FromServerFnError, { - async fn from_req(req: Request) -> Result> { + async fn from_req(req: Request) -> Result { let string_data = req.as_query().unwrap_or_default(); let args = serde_qs::Config::new(5, false) .deserialize_str::(string_data) - .map_err(|e| ServerFnError::Args(e.to_string()))?; + .map_err(|e| E::from(ServerFnErrorErr::Args(e.to_string())))?; Ok(args) } } @@ -52,32 +51,31 @@ impl Encoding for PostUrl { const METHOD: Method = Method::POST; } -impl IntoReq for T +impl IntoReq for T where - Request: ClientReq, + Request: ClientReq, T: Serialize + Send, + E: FromServerFnError, { - fn into_req( - self, - path: &str, - accepts: &str, - ) -> Result> { - let qs = serde_qs::to_string(&self) - .map_err(|e| ServerFnError::Serialization(e.to_string()))?; + fn into_req(self, path: &str, accepts: &str) -> Result { + let qs = serde_qs::to_string(&self).map_err(|e| { + E::from(ServerFnErrorErr::Serialization(e.to_string())) + })?; Request::try_new_post(path, accepts, PostUrl::CONTENT_TYPE, qs) } } -impl FromReq for T +impl FromReq for T where - Request: Req + Send + 'static, + Request: Req + Send + 'static, T: DeserializeOwned, + E: FromServerFnError, { - async fn from_req(req: Request) -> Result> { + async fn from_req(req: Request) -> Result { let string_data = req.try_into_string().await?; let args = serde_qs::Config::new(5, false) .deserialize_str::(&string_data) - .map_err(|e| ServerFnError::Args(e.to_string()))?; + .map_err(|e| E::from(ServerFnErrorErr::Args(e.to_string())))?; Ok(args) } } @@ -86,18 +84,18 @@ where impl Codec for T where T: DeserializeOwned + Serialize + Send, - Request: Req + Send, - Response: Res + Send, + Request: Req + Send, + Response: Res + Send, { - async fn from_req(req: Request) -> Result> { + async fn from_req(req: Request) -> Result> { let string_data = req.try_into_string()?; let args = serde_json::from_str::(&string_data) - .map_err(|e| ServerFnError::Args(e.to_string()))?; + .map_err(|e| ServerFnErrorErr::Args(e.to_string()).into())?; Ok(args) } - async fn into_req(self) -> Result> { + async fn into_req(self) -> Result> { /* let qs = serde_qs::to_string(&self)?; let req = http::Request::builder() .method("GET") @@ -110,7 +108,7 @@ where todo!() } - async fn from_res(res: Response) -> Result> { + async fn from_res(res: Response) -> Result> { todo!() /* let (_parts, body) = res.into_parts(); @@ -118,7 +116,7 @@ where .collect() .await .map(|c| c.to_bytes()) - .map_err(|e| ServerFnError::Deserialization(e.to_string()))?; + .map_err(|e| ServerFnErrorErr::Deserialization(e.to_string()).into())?; let string_data = String::from_utf8(body_bytes.to_vec())?; serde_json::from_str(&string_data) .map_err(|e| ServerFnError::Deserialization(e.to_string())) */ diff --git a/server_fn/src/error.rs b/server_fn/src/error.rs index c139dde062..aa1781fb98 100644 --- a/server_fn/src/error.rs +++ b/server_fn/src/error.rs @@ -1,4 +1,7 @@ -use serde::{Deserialize, Serialize}; +#![allow(deprecated)] + +use base64::{engine::general_purpose::URL_SAFE, Engine as _}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::{ fmt, fmt::{Display, Write}, @@ -35,6 +38,11 @@ impl From for Error { feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) )] +#[deprecated( + since = "0.8.0", + note = "Now server_fn can return any error type other than ServerFnError, \ + so the WrappedServerError variant will be removed in 0.9.0" +)] pub struct NoCustomError; // Implement `Display` for `NoCustomError` @@ -55,11 +63,21 @@ impl FromStr for NoCustomError { /// Wraps some error type, which may implement any of [`Error`](trait@std::error::Error), [`Clone`], or /// [`Display`]. #[derive(Debug)] +#[deprecated( + since = "0.8.0", + note = "Now server_fn can return any error type other than ServerFnError, \ + so the WrappedServerError variant will be removed in 0.9.0" +)] pub struct WrapError(pub T); /// A helper macro to convert a variety of different types into `ServerFnError`. /// This should mostly be used if you are implementing `From` for `YourError`. #[macro_export] +#[deprecated( + since = "0.8.0", + note = "Now server_fn can return any error type other than ServerFnError, \ + so the WrappedServerError variant will be removed in 0.9.0" +)] macro_rules! server_fn_error { () => {{ use $crate::{ViaError, WrapError}; @@ -75,6 +93,12 @@ macro_rules! server_fn_error { /// This trait serves as the conversion method between a variety of types /// and [`ServerFnError`]. +#[deprecated( + since = "0.8.0", + note = "Now server_fn can return any error type other than ServerFnError, \ + so users should place their custom error type instead of \ + ServerFnError" +)] pub trait ViaError { /// Converts something into an error. fn to_server_error(&self) -> ServerFnError; @@ -90,6 +114,7 @@ impl ViaError } // A type tag for ServerFnError so we can special case it +#[deprecated] pub(crate) trait ServerFnErrorKind {} impl ServerFnErrorKind for ServerFnError {} @@ -132,6 +157,7 @@ impl ViaError for WrapError { } /// Type for errors that can occur when using server functions. +/// This type is intended to be used as the return type of the server function for easy error conversion with `?` operator. /// /// Unlike [`ServerFnErrorErr`], this does not implement [`Error`](trait@std::error::Error). /// This means that other error types can easily be converted into it using the @@ -142,6 +168,12 @@ impl ViaError for WrapError { derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) )] pub enum ServerFnError { + #[deprecated( + since = "0.8.0", + note = "Now server_fn can return any error type other than \ + ServerFnError, so users should place their custom error type \ + instead of ServerFnError" + )] /// A user-defined custom error type, which defaults to [`NoCustomError`]. WrappedServerError(E), /// Error while trying to register the server function (only occurs in case of poisoned RwLock). @@ -152,6 +184,8 @@ pub enum ServerFnError { Response(String), /// Occurs when there is an error while actually running the function on the server. ServerError(String), + /// Occurs when there is an error while actually running the middleware on the server. + MiddlewareError(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. @@ -198,6 +232,8 @@ where ), ServerFnError::ServerError(s) => format!("error running server function: {s}"), + ServerFnError::MiddlewareError(s) => + format!("error running middleware: {s}"), ServerFnError::Deserialization(s) => format!("error deserializing server function results: {s}"), ServerFnError::Serialization(s) => @@ -224,39 +260,47 @@ where /// /// This is implemented for the default [`ServerFnError`], which uses [`NoCustomError`]. pub trait ServerFnErrorSerde: Sized { + /// The error type that can occur when serializing the custom error type. + type Error: std::error::Error; + /// Converts the custom error type to a [`String`]. - fn ser(&self) -> Result; + fn ser(&self) -> Result; /// Deserializes the custom error type from a [`String`]. fn de(data: &str) -> Self; } -impl ServerFnErrorSerde for ServerFnError +impl ServerFnErrorSerde for ServerFnErrorErr where CustErr: FromStr + Display, { - fn ser(&self) -> Result { + type Error = std::fmt::Error; + + fn ser(&self) -> Result { let mut buf = String::new(); match self { - ServerFnError::WrappedServerError(e) => { + ServerFnErrorErr::WrappedServerError(e) => { write!(&mut buf, "WrappedServerFn|{e}") } - ServerFnError::Registration(e) => { + ServerFnErrorErr::Registration(e) => { write!(&mut buf, "Registration|{e}") } - ServerFnError::Request(e) => write!(&mut buf, "Request|{e}"), - ServerFnError::Response(e) => write!(&mut buf, "Response|{e}"), - ServerFnError::ServerError(e) => { + ServerFnErrorErr::Request(e) => write!(&mut buf, "Request|{e}"), + ServerFnErrorErr::Response(e) => write!(&mut buf, "Response|{e}"), + ServerFnErrorErr::ServerError(e) => { write!(&mut buf, "ServerError|{e}") } - ServerFnError::Deserialization(e) => { + ServerFnErrorErr::MiddlewareError(e) => { + write!(&mut buf, "MiddlewareError|{e}") + } + ServerFnErrorErr::Deserialization(e) => { write!(&mut buf, "Deserialization|{e}") } - ServerFnError::Serialization(e) => { + ServerFnErrorErr::Serialization(e) => { write!(&mut buf, "Serialization|{e}") } - ServerFnError::Args(e) => write!(&mut buf, "Args|{e}"), - ServerFnError::MissingArg(e) => { + ServerFnErrorErr::Args(e) => write!(&mut buf, "Args|{e}"), + ServerFnErrorErr::MissingArg(e) => { write!(&mut buf, "MissingArg|{e}") } }?; @@ -267,31 +311,33 @@ where data.split_once('|') .and_then(|(ty, data)| match ty { "WrappedServerFn" => match CustErr::from_str(data) { - Ok(d) => Some(ServerFnError::WrappedServerError(d)), + Ok(d) => Some(ServerFnErrorErr::WrappedServerError(d)), Err(_) => None, }, "Registration" => { - Some(ServerFnError::Registration(data.to_string())) + Some(ServerFnErrorErr::Registration(data.to_string())) + } + "Request" => Some(ServerFnErrorErr::Request(data.to_string())), + "Response" => { + Some(ServerFnErrorErr::Response(data.to_string())) } - "Request" => Some(ServerFnError::Request(data.to_string())), - "Response" => Some(ServerFnError::Response(data.to_string())), "ServerError" => { - Some(ServerFnError::ServerError(data.to_string())) + Some(ServerFnErrorErr::ServerError(data.to_string())) } "Deserialization" => { - Some(ServerFnError::Deserialization(data.to_string())) + Some(ServerFnErrorErr::Deserialization(data.to_string())) } "Serialization" => { - Some(ServerFnError::Serialization(data.to_string())) + Some(ServerFnErrorErr::Serialization(data.to_string())) } - "Args" => Some(ServerFnError::Args(data.to_string())), + "Args" => Some(ServerFnErrorErr::Args(data.to_string())), "MissingArg" => { - Some(ServerFnError::MissingArg(data.to_string())) + Some(ServerFnErrorErr::MissingArg(data.to_string())) } _ => None, }) .unwrap_or_else(|| { - ServerFnError::Deserialization(format!( + ServerFnErrorErr::Deserialization(format!( "Could not deserialize error {data:?}" )) }) @@ -311,7 +357,7 @@ where } } -/// Type for errors that can occur when using server functions. +/// Type for errors that can occur when using server functions. If you need to return a custom error type from a server function, implement `From` for your custom error type. /// /// Unlike [`ServerFnError`], this implements [`std::error::Error`]. This means /// it can be used in situations in which the `Error` trait is required, but it’s @@ -334,6 +380,9 @@ pub enum ServerFnErrorErr { /// Occurs when there is an error while actually running the function on the server. #[error("error running server function: {0}")] ServerError(String), + /// Occurs when there is an error while actually running the middleware on the server. + #[error("error running middleware: {0}")] + MiddlewareError(String), /// Occurs on the client if there is an error deserializing the server's response. #[error("error deserializing server function results: {0}")] Deserialization(String), @@ -361,6 +410,9 @@ impl From> for ServerFnErrorErr { ServerFnError::ServerError(value) => { ServerFnErrorErr::ServerError(value) } + ServerFnError::MiddlewareError(value) => { + ServerFnErrorErr::MiddlewareError(value) + } ServerFnError::Deserialization(value) => { ServerFnErrorErr::Deserialization(value) } @@ -386,15 +438,15 @@ impl From> for ServerFnErrorErr { /// without JavaScript/WASM supported, by encoding it in the URL as a query string. /// This is useful for progressive enhancement. #[derive(Debug)] -pub struct ServerFnUrlError { +pub struct ServerFnUrlError { path: String, - error: ServerFnError, + error: E, } -impl ServerFnUrlError { +impl ServerFnUrlError { /// Creates a new structure associating the server function at some path /// with a particular error. - pub fn new(path: impl Display, error: ServerFnError) -> Self { + pub fn new(path: impl Display, error: E) -> Self { Self { path: path.to_string(), error, @@ -402,7 +454,7 @@ impl ServerFnUrlError { } /// The error itself. - pub fn error(&self) -> &ServerFnError { + pub fn error(&self) -> &E { &self.error } @@ -412,16 +464,13 @@ impl ServerFnUrlError { } /// Adds an encoded form of this server function error to the given base URL. - pub fn to_url(&self, base: &str) -> Result - where - CustErr: FromStr + Display, - { + pub fn to_url(&self, base: &str) -> Result { let mut url = Url::parse(base)?; url.query_pairs_mut() .append_pair("__path", &self.path) .append_pair( "__err", - &ServerFnErrorSerde::ser(&self.error).unwrap_or_default(), + &URL_SAFE.encode(self.error.ser().unwrap_or_default()), ); Ok(url) } @@ -448,16 +497,93 @@ impl ServerFnUrlError { *path = url.to_string(); } } + + /// Decodes an error from a URL. + pub fn decode_err(err: &str) -> E { + let decoded = match URL_SAFE.decode(err) { + Ok(decoded) => decoded, + Err(err) => { + return ServerFnErrorErr::Deserialization(err.to_string()) + .into() + } + }; + let s = match String::from_utf8(decoded) { + Ok(s) => s, + Err(err) => { + return ServerFnErrorErr::Deserialization(err.to_string()) + .into() + } + }; + E::de(&s) + } } -impl From> for ServerFnError { - fn from(error: ServerFnUrlError) -> Self { +impl From> for ServerFnError { + fn from(error: ServerFnUrlError) -> Self { + error.error.into() + } +} + +impl From>> for ServerFnError { + fn from(error: ServerFnUrlError>) -> Self { error.error } } -impl From> for ServerFnErrorErr { - fn from(error: ServerFnUrlError) -> Self { - error.error.into() +#[derive(Debug)] +#[doc(hidden)] +/// Only used instantly only when a framework needs E: Error. +pub struct ServerFnErrorWrapper(pub E); + +impl Display for ServerFnErrorWrapper { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl std::error::Error for ServerFnErrorWrapper { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } +} + +/// A trait for types that can be returned from a server function. +pub trait FromServerFnError: + Display + + std::fmt::Debug + + ServerFnErrorSerde + + From + + 'static +{ +} + +#[test] +fn assert_from_server_fn_error_impl() { + fn assert_impl() {} + + assert_impl::(); +} + +impl FromServerFnError for E where + E: Display + + std::fmt::Debug + + ServerFnErrorSerde + + From + + 'static +{ +} + +impl ServerFnErrorSerde for E +where + E: Serialize + DeserializeOwned, +{ + type Error = serde_json::Error; + + fn ser(&self) -> Result { + serde_json::to_string(self) + } + + fn de(data: &str) -> Self { + serde_json::from_str(data).unwrap() } } diff --git a/server_fn/src/lib.rs b/server_fn/src/lib.rs index 5b656d33a1..bd2ba5afa5 100644 --- a/server_fn/src/lib.rs +++ b/server_fn/src/lib.rs @@ -131,10 +131,9 @@ use codec::{Encoding, FromReq, FromRes, IntoReq, IntoRes}; #[doc(hidden)] pub use const_format; use dashmap::DashMap; -pub use error::ServerFnError; -use error::ServerFnErrorSerde; #[cfg(feature = "form-redirects")] use error::ServerFnUrlError; +use error::{FromServerFnError, ServerFnErrorSerde}; use http::Method; use middleware::{Layer, Service}; use once_cell::sync::Lazy; @@ -148,7 +147,7 @@ pub use serde; #[doc(hidden)] #[cfg(feature = "serde-lite")] pub use serde_lite; -use std::{fmt::Display, future::Future, pin::Pin, str::FromStr, sync::Arc}; +use std::{future::Future, pin::Pin, sync::Arc}; #[doc(hidden)] pub use xxhash_rust; @@ -222,9 +221,8 @@ where /// The [`Encoding`] used in the response for the result of the server function. type OutputEncoding: Encoding; - /// The type of the custom error on [`ServerFnError`], if any. (If there is no - /// custom error type, this can be `NoCustomError` by default.) - type Error: FromStr + Display; + /// The type of the error on the server function. Typically [`ServerFnError`], but allowed to be any type that implements [`FromServerFnError`]. + type Error: FromServerFnError; /// Returns [`Self::PATH`]. fn url() -> &'static str { @@ -240,7 +238,7 @@ where /// The body of the server function. This will only run on the server. fn run_body( self, - ) -> impl Future>> + Send; + ) -> impl Future> + Send; #[doc(hidden)] fn run_on_server( @@ -298,8 +296,7 @@ where #[doc(hidden)] fn run_on_client( self, - ) -> impl Future>> + Send - { + ) -> impl Future> + Send { async move { // create and send request on client let req = @@ -313,8 +310,7 @@ where fn run_on_client_with_req( req: >::Request, redirect_hook: Option<&RedirectHook>, - ) -> impl Future>> + Send - { + ) -> impl Future> + Send { async move { let res = Self::Client::send(req).await?; @@ -325,7 +321,7 @@ where // if it returns an error status, deserialize the error using FromStr let res = if (400..=599).contains(&status) { let text = res.try_into_string().await?; - Err(ServerFnError::::de(&text)) + Err(Self::Error::de(&text)) } else { // otherwise, deserialize the body as is Ok(Self::Output::from_res(res).await) @@ -345,9 +341,8 @@ where #[doc(hidden)] fn execute_on_server( req: Self::ServerRequest, - ) -> impl Future< - Output = Result>, - > + Send { + ) -> impl Future> + Send + { async { let this = Self::from_req(req).await?; let output = this.run_body().await?; diff --git a/server_fn/src/middleware/mod.rs b/server_fn/src/middleware/mod.rs index 0789bf719a..dba6d4c204 100644 --- a/server_fn/src/middleware/mod.rs +++ b/server_fn/src/middleware/mod.rs @@ -29,20 +29,19 @@ pub trait Service { #[cfg(feature = "axum-no-default")] mod axum { use super::{BoxedService, Service}; - use crate::{response::Res, ServerFnError}; + use crate::{ + error::{FromServerFnError, ServerFnErrorErr}, + response::Res, + }; use axum::body::Body; use http::{Request, Response}; - use std::{ - fmt::{Debug, Display}, - future::Future, - pin::Pin, - }; + use std::{future::Future, pin::Pin}; impl super::Service, Response> for S where S: tower::Service, Response = Response>, S::Future: Send + 'static, - S::Error: Into + Send + Debug + Display + Sync + 'static, + S::Error: FromServerFnError + Send + Sync + 'static, { fn run( &mut self, @@ -52,7 +51,9 @@ mod axum { let inner = self.call(req); Box::pin(async move { inner.await.unwrap_or_else(|e| { - let err = ServerFnError::new(e); + let err = S::Error::from( + ServerFnErrorErr::MiddlewareError(e.to_string()), + ); Response::::error_response(&path, &err) }) }) @@ -63,7 +64,7 @@ mod axum { for BoxedService, Response> { type Response = Response; - type Error = ServerFnError; + type Error = ServerFnErrorErr; type Future = Pin< Box< dyn std::future::Future< @@ -105,22 +106,18 @@ mod axum { #[cfg(feature = "actix")] mod actix { use crate::{ + error::{FromServerFnError, ServerFnErrorErr}, request::actix::ActixRequest, response::{actix::ActixResponse, Res}, - ServerFnError, }; use actix_web::{HttpRequest, HttpResponse}; - use std::{ - fmt::{Debug, Display}, - future::Future, - pin::Pin, - }; + use std::{future::Future, pin::Pin}; impl super::Service for S where S: actix_web::dev::Service, S::Future: Send + 'static, - S::Error: Into + Debug + Display + 'static, + S::Error: FromServerFnError, { fn run( &mut self, @@ -130,7 +127,9 @@ mod actix { let inner = self.call(req); Box::pin(async move { inner.await.unwrap_or_else(|e| { - let err = ServerFnError::new(e); + let err = S::Error::from( + ServerFnErrorErr::MiddlewareError(e.to_string()), + ); ActixResponse::error_response(&path, &err).take() }) }) @@ -141,7 +140,7 @@ mod actix { where S: actix_web::dev::Service, S::Future: Send + 'static, - S::Error: Into + Debug + Display + 'static, + S::Error: FromServerFnError, { fn run( &mut self, @@ -151,7 +150,9 @@ mod actix { let inner = self.call(req.0.take().0); Box::pin(async move { ActixResponse::from(inner.await.unwrap_or_else(|e| { - let err = ServerFnError::new(e); + let err = S::Error::from( + ServerFnErrorErr::MiddlewareError(e.to_string()), + ); ActixResponse::error_response(&path, &err).take() })) }) diff --git a/server_fn/src/request/actix.rs b/server_fn/src/request/actix.rs index 90e8e1f26a..98365f9575 100644 --- a/server_fn/src/request/actix.rs +++ b/server_fn/src/request/actix.rs @@ -1,4 +1,7 @@ -use crate::{error::ServerFnError, request::Req}; +use crate::{ + error::{FromServerFnError, ServerFnErrorErr}, + request::Req, +}; use actix_web::{web::Payload, HttpRequest}; use bytes::Bytes; use futures::Stream; @@ -33,9 +36,9 @@ impl From<(HttpRequest, Payload)> for ActixRequest { } } -impl Req for ActixRequest +impl Req for ActixRequest where - CustErr: 'static, + E: FromServerFnError, { fn as_query(&self) -> Option<&str> { self.0 .0.uri().query() @@ -53,44 +56,34 @@ where self.header("Referer") } - fn try_into_bytes( - self, - ) -> impl Future>> + Send - { + fn try_into_bytes(self) -> impl Future> + Send { // Actix is going to keep this on a single thread anyway so it's fine to wrap it // with SendWrapper, which makes it `Send` but will panic if it moves to another thread SendWrapper::new(async move { let payload = self.0.take().1; - payload - .to_bytes() - .await - .map_err(|e| ServerFnError::Deserialization(e.to_string())) + payload.to_bytes().await.map_err(|e| { + ServerFnErrorErr::Deserialization(e.to_string()).into() + }) }) } - fn try_into_string( - self, - ) -> impl Future>> + Send - { + fn try_into_string(self) -> impl Future> + Send { // Actix is going to keep this on a single thread anyway so it's fine to wrap it // with SendWrapper, which makes it `Send` but will panic if it moves to another thread SendWrapper::new(async move { let payload = self.0.take().1; - let bytes = payload - .to_bytes() - .await - .map_err(|e| ServerFnError::Deserialization(e.to_string()))?; - String::from_utf8(bytes.into()) - .map_err(|e| ServerFnError::Deserialization(e.to_string())) + let bytes = payload.to_bytes().await.map_err(|e| { + E::from(ServerFnErrorErr::Deserialization(e.to_string())) + })?; + String::from_utf8(bytes.into()).map_err(|e| { + E::from(ServerFnErrorErr::Deserialization(e.to_string())) + }) }) } fn try_into_stream( self, - ) -> Result< - impl Stream> + Send, - ServerFnError, - > { + ) -> Result> + Send, E> { Ok(futures::stream::once(async { todo!() })) } } diff --git a/server_fn/src/request/axum.rs b/server_fn/src/request/axum.rs index e26f7c7676..1ae63c3f1b 100644 --- a/server_fn/src/request/axum.rs +++ b/server_fn/src/request/axum.rs @@ -1,4 +1,7 @@ -use crate::{error::ServerFnError, request::Req}; +use crate::{ + error::{FromServerFnError, ServerFnErrorErr}, + request::Req, +}; use axum::body::{Body, Bytes}; use futures::{Stream, StreamExt}; use http::{ @@ -8,9 +11,9 @@ use http::{ use http_body_util::BodyExt; use std::borrow::Cow; -impl Req for Request +impl Req for Request where - CustErr: 'static, + E: FromServerFnError, { fn as_query(&self) -> Option<&str> { self.uri().query() @@ -34,29 +37,28 @@ where .map(|h| String::from_utf8_lossy(h.as_bytes())) } - async fn try_into_bytes(self) -> Result> { + async fn try_into_bytes(self) -> Result { let (_parts, body) = self.into_parts(); - body.collect() - .await - .map(|c| c.to_bytes()) - .map_err(|e| ServerFnError::Deserialization(e.to_string())) + body.collect().await.map(|c| c.to_bytes()).map_err(|e| { + ServerFnErrorErr::Deserialization(e.to_string()).into() + }) } - async fn try_into_string(self) -> Result> { + async fn try_into_string(self) -> Result { let bytes = self.try_into_bytes().await?; - String::from_utf8(bytes.to_vec()) - .map_err(|e| ServerFnError::Deserialization(e.to_string())) + String::from_utf8(bytes.to_vec()).map_err(|e| { + ServerFnErrorErr::Deserialization(e.to_string()).into() + }) } fn try_into_stream( self, - ) -> Result< - impl Stream> + Send + 'static, - ServerFnError, - > { + ) -> Result> + Send + 'static, E> { Ok(self.into_body().into_data_stream().map(|chunk| { - chunk.map_err(|e| ServerFnError::Deserialization(e.to_string())) + chunk.map_err(|e| { + ServerFnErrorErr::Deserialization(e.to_string()).into() + }) })) } } diff --git a/server_fn/src/request/browser.rs b/server_fn/src/request/browser.rs index 550b898cdf..79d732bd35 100644 --- a/server_fn/src/request/browser.rs +++ b/server_fn/src/request/browser.rs @@ -1,5 +1,8 @@ use super::ClientReq; -use crate::{client::get_server_url, error::ServerFnError}; +use crate::{ + client::get_server_url, + error::{FromServerFnError, ServerFnErrorErr}, +}; use bytes::Bytes; use futures::{Stream, StreamExt}; pub use gloo_net::http::Request; @@ -83,7 +86,10 @@ fn abort_signal() -> (Option, Option) { (ctrl.map(|ctrl| AbortOnDrop(Some(ctrl))), signal) } -impl ClientReq for BrowserRequest { +impl ClientReq for BrowserRequest +where + E: FromServerFnError, +{ type FormData = BrowserFormData; fn try_new_get( @@ -91,7 +97,7 @@ impl ClientReq for BrowserRequest { accepts: &str, content_type: &str, query: &str, - ) -> Result> { + ) -> Result { let (abort_ctrl, abort_signal) = abort_signal(); let server_url = get_server_url(); let mut url = String::with_capacity( @@ -107,7 +113,9 @@ impl ClientReq for BrowserRequest { .header("Accept", accepts) .abort_signal(abort_signal.as_ref()) .build() - .map_err(|e| ServerFnError::Request(e.to_string()))?, + .map_err(|e| { + E::from(ServerFnErrorErr::Request(e.to_string())) + })?, abort_ctrl, }))) } @@ -117,7 +125,7 @@ impl ClientReq for BrowserRequest { accepts: &str, content_type: &str, body: String, - ) -> Result> { + ) -> Result { let (abort_ctrl, abort_signal) = abort_signal(); let server_url = get_server_url(); let mut url = String::with_capacity(server_url.len() + path.len()); @@ -129,7 +137,9 @@ impl ClientReq for BrowserRequest { .header("Accept", accepts) .abort_signal(abort_signal.as_ref()) .body(body) - .map_err(|e| ServerFnError::Request(e.to_string()))?, + .map_err(|e| { + E::from(ServerFnErrorErr::Request(e.to_string())) + })?, abort_ctrl, }))) } @@ -139,7 +149,7 @@ impl ClientReq for BrowserRequest { accepts: &str, content_type: &str, body: Bytes, - ) -> Result> { + ) -> Result { let (abort_ctrl, abort_signal) = abort_signal(); let server_url = get_server_url(); let mut url = String::with_capacity(server_url.len() + path.len()); @@ -153,7 +163,9 @@ impl ClientReq for BrowserRequest { .header("Accept", accepts) .abort_signal(abort_signal.as_ref()) .body(body) - .map_err(|e| ServerFnError::Request(e.to_string()))?, + .map_err(|e| { + E::from(ServerFnErrorErr::Request(e.to_string())) + })?, abort_ctrl, }))) } @@ -162,7 +174,7 @@ impl ClientReq for BrowserRequest { path: &str, accepts: &str, body: Self::FormData, - ) -> Result> { + ) -> Result { let (abort_ctrl, abort_signal) = abort_signal(); let server_url = get_server_url(); let mut url = String::with_capacity(server_url.len() + path.len()); @@ -173,7 +185,9 @@ impl ClientReq for BrowserRequest { .header("Accept", accepts) .abort_signal(abort_signal.as_ref()) .body(body.0.take()) - .map_err(|e| ServerFnError::Request(e.to_string()))?, + .map_err(|e| { + E::from(ServerFnErrorErr::Request(e.to_string())) + })?, abort_ctrl, }))) } @@ -183,17 +197,17 @@ impl ClientReq for BrowserRequest { accepts: &str, content_type: &str, body: Self::FormData, - ) -> Result> { + ) -> Result { let (abort_ctrl, abort_signal) = abort_signal(); let form_data = body.0.take(); let url_params = UrlSearchParams::new_with_str_sequence_sequence(&form_data) .map_err(|e| { - ServerFnError::Serialization(e.as_string().unwrap_or_else( - || { + E::from(ServerFnErrorErr::Serialization( + e.as_string().unwrap_or_else(|| { "Could not serialize FormData to URLSearchParams" .to_string() - }, + }), )) })?; Ok(Self(SendWrapper::new(RequestInner { @@ -202,7 +216,9 @@ impl ClientReq for BrowserRequest { .header("Accept", accepts) .abort_signal(abort_signal.as_ref()) .body(url_params) - .map_err(|e| ServerFnError::Request(e.to_string()))?, + .map_err(|e| { + E::from(ServerFnErrorErr::Request(e.to_string())) + })?, abort_ctrl, }))) } @@ -212,11 +228,12 @@ impl ClientReq for BrowserRequest { accepts: &str, content_type: &str, body: impl Stream + 'static, - ) -> Result> { + ) -> Result { // TODO abort signal let (request, abort_ctrl) = - streaming_request(path, accepts, content_type, body) - .map_err(|e| ServerFnError::Request(format!("{e:?}")))?; + streaming_request(path, accepts, content_type, body).map_err( + |e| E::from(ServerFnErrorErr::Request(format!("{e:?}"))), + )?; Ok(Self(SendWrapper::new(RequestInner { request, abort_ctrl, diff --git a/server_fn/src/request/generic.rs b/server_fn/src/request/generic.rs index da1add07ff..e8453e3dc8 100644 --- a/server_fn/src/request/generic.rs +++ b/server_fn/src/request/generic.rs @@ -12,7 +12,10 @@ //! * `wasm32-wasip*` integration crate `leptos_wasi` is using this //! crate under the hood. -use crate::request::Req; +use crate::{ + error::{FromServerFnError, ServerFnErrorErr}, + request::Req, +}; use bytes::Bytes; use futures::{ stream::{self, Stream}, @@ -21,30 +24,23 @@ use futures::{ use http::Request; use std::borrow::Cow; -impl Req for Request +impl Req for Request where - CustErr: 'static, + E: FromServerFnError, { - async fn try_into_bytes( - self, - ) -> Result> { + async fn try_into_bytes(self) -> Result { Ok(self.into_body()) } - async fn try_into_string( - self, - ) -> Result> { + async fn try_into_string(self) -> Result { String::from_utf8(self.into_body().into()).map_err(|err| { - crate::ServerFnError::Deserialization(err.to_string()) + ServerFnErrorErr::Deserialization(err.to_string()).into() }) } fn try_into_stream( self, - ) -> Result< - impl Stream> + Send + 'static, - crate::ServerFnError, - > { + ) -> Result> + Send + 'static, E> { Ok(stream::iter(self.into_body()) .ready_chunks(16) .map(|chunk| Ok(Bytes::from(chunk)))) diff --git a/server_fn/src/request/mod.rs b/server_fn/src/request/mod.rs index 3a4c71d393..f8340414c5 100644 --- a/server_fn/src/request/mod.rs +++ b/server_fn/src/request/mod.rs @@ -1,4 +1,3 @@ -use crate::error::ServerFnError; use bytes::Bytes; use futures::Stream; use std::{borrow::Cow, future::Future}; @@ -19,7 +18,7 @@ pub mod generic; pub mod reqwest; /// Represents a request as made by the client. -pub trait ClientReq +pub trait ClientReq where Self: Sized, { @@ -32,7 +31,7 @@ where content_type: &str, accepts: &str, query: &str, - ) -> Result>; + ) -> Result; /// Attempts to construct a new `POST` request with a text body. fn try_new_post( @@ -40,7 +39,7 @@ where content_type: &str, accepts: &str, body: String, - ) -> Result>; + ) -> Result; /// Attempts to construct a new `POST` request with a binary body. fn try_new_post_bytes( @@ -48,7 +47,7 @@ where content_type: &str, accepts: &str, body: Bytes, - ) -> Result>; + ) -> Result; /// Attempts to construct a new `POST` request with form data as the body. fn try_new_post_form_data( @@ -56,14 +55,14 @@ where accepts: &str, content_type: &str, body: Self::FormData, - ) -> Result>; + ) -> Result; /// Attempts to construct a new `POST` request with a multipart body. fn try_new_multipart( path: &str, accepts: &str, body: Self::FormData, - ) -> Result>; + ) -> Result; /// Attempts to construct a new `POST` request with a streaming body. fn try_new_streaming( @@ -71,11 +70,11 @@ where accepts: &str, content_type: &str, body: impl Stream + Send + 'static, - ) -> Result>; + ) -> Result; } /// Represents the request as received by the server. -pub trait Req +pub trait Req where Self: Sized, { @@ -92,32 +91,22 @@ where fn referer(&self) -> Option>; /// Attempts to extract the body of the request into [`Bytes`]. - fn try_into_bytes( - self, - ) -> impl Future>> + Send; + fn try_into_bytes(self) -> impl Future> + Send; /// Attempts to convert the body of the request into a string. - fn try_into_string( - self, - ) -> impl Future>> + Send; + fn try_into_string(self) -> impl Future> + Send; /// Attempts to convert the body of the request into a stream of bytes. fn try_into_stream( self, - ) -> Result< - impl Stream> + Send + 'static, - ServerFnError, - >; + ) -> Result> + Send + 'static, E>; } /// A mocked request type that can be used in place of the actual server request, /// when compiling for the browser. pub struct BrowserMockReq; -impl Req for BrowserMockReq -where - CustErr: 'static, -{ +impl Req for BrowserMockReq { fn as_query(&self) -> Option<&str> { unreachable!() } @@ -133,20 +122,17 @@ where fn referer(&self) -> Option> { unreachable!() } - async fn try_into_bytes(self) -> Result> { + async fn try_into_bytes(self) -> Result { unreachable!() } - async fn try_into_string(self) -> Result> { + async fn try_into_string(self) -> Result { unreachable!() } fn try_into_stream( self, - ) -> Result< - impl Stream> + Send, - ServerFnError, - > { + ) -> Result> + Send, E> { Ok(futures::stream::once(async { unreachable!() })) } } diff --git a/server_fn/src/request/reqwest.rs b/server_fn/src/request/reqwest.rs index 1352da2fc1..6159c65ee7 100644 --- a/server_fn/src/request/reqwest.rs +++ b/server_fn/src/request/reqwest.rs @@ -1,5 +1,8 @@ use super::ClientReq; -use crate::{client::get_server_url, error::ServerFnError}; +use crate::{ + client::get_server_url, + error::{FromServerFnError, ServerFnErrorErr}, +}; use bytes::Bytes; use futures::Stream; use once_cell::sync::Lazy; @@ -8,7 +11,10 @@ pub use reqwest::{multipart::Form, Client, Method, Request, Url}; pub(crate) static CLIENT: Lazy = Lazy::new(Client::new); -impl ClientReq for Request { +impl ClientReq for Request +where + E: FromServerFnError, +{ type FormData = Form; fn try_new_get( @@ -16,17 +22,17 @@ impl ClientReq for Request { accepts: &str, content_type: &str, query: &str, - ) -> Result> { + ) -> Result { let url = format!("{}{}", get_server_url(), path); let mut url = Url::try_from(url.as_str()) - .map_err(|e| ServerFnError::Request(e.to_string()))?; + .map_err(|e| E::from(ServerFnErrorErr::Request(e.to_string())))?; url.set_query(Some(query)); let req = CLIENT .get(url) .header(CONTENT_TYPE, content_type) .header(ACCEPT, accepts) .build() - .map_err(|e| ServerFnError::Request(e.to_string()))?; + .map_err(|e| E::from(ServerFnErrorErr::Request(e.to_string())))?; Ok(req) } @@ -35,7 +41,7 @@ impl ClientReq for Request { accepts: &str, content_type: &str, body: String, - ) -> Result> { + ) -> Result { let url = format!("{}{}", get_server_url(), path); CLIENT .post(url) @@ -43,7 +49,7 @@ impl ClientReq for Request { .header(ACCEPT, accepts) .body(body) .build() - .map_err(|e| ServerFnError::Request(e.to_string())) + .map_err(|e| ServerFnErrorErr::Request(e.to_string()).into()) } fn try_new_post_bytes( @@ -51,7 +57,7 @@ impl ClientReq for Request { accepts: &str, content_type: &str, body: Bytes, - ) -> Result> { + ) -> Result { let url = format!("{}{}", get_server_url(), path); CLIENT .post(url) @@ -59,20 +65,20 @@ impl ClientReq for Request { .header(ACCEPT, accepts) .body(body) .build() - .map_err(|e| ServerFnError::Request(e.to_string())) + .map_err(|e| ServerFnErrorErr::Request(e.to_string()).into()) } fn try_new_multipart( path: &str, accepts: &str, body: Self::FormData, - ) -> Result> { + ) -> Result { CLIENT .post(path) .header(ACCEPT, accepts) .multipart(body) .build() - .map_err(|e| ServerFnError::Request(e.to_string())) + .map_err(|e| ServerFnErrorErr::Request(e.to_string()).into()) } fn try_new_post_form_data( @@ -80,14 +86,14 @@ impl ClientReq for Request { accepts: &str, content_type: &str, body: Self::FormData, - ) -> Result> { + ) -> Result { CLIENT .post(path) .header(CONTENT_TYPE, content_type) .header(ACCEPT, accepts) .multipart(body) .build() - .map_err(|e| ServerFnError::Request(e.to_string())) + .map_err(|e| ServerFnErrorErr::Request(e.to_string()).into()) } fn try_new_streaming( @@ -95,7 +101,7 @@ impl ClientReq for Request { _accepts: &str, _content_type: &str, _body: impl Stream + 'static, - ) -> Result> { + ) -> Result { todo!("Streaming requests are not yet implemented for reqwest.") // We run into a fundamental issue here. // To be a reqwest body, the type must be Sync @@ -112,7 +118,7 @@ impl ClientReq for Request { .header(ACCEPT, accepts) .body(body) .build() - .map_err(|e| ServerFnError::Request(e.to_string())) + .map_err(|e| ServerFnErrorErr::Request(e.to_string()).into()) }*/ } } diff --git a/server_fn/src/request/spin.rs b/server_fn/src/request/spin.rs index 58781343d5..f819657a30 100644 --- a/server_fn/src/request/spin.rs +++ b/server_fn/src/request/spin.rs @@ -8,7 +8,7 @@ use http::{ use http_body_util::BodyExt; use std::borrow::Cow; -impl Req for IncomingRequest +impl Req for IncomingRequest where CustErr: 'static, { @@ -34,29 +34,31 @@ where .map(|h| String::from_utf8_lossy(h.as_bytes())) } - async fn try_into_bytes(self) -> Result> { + async fn try_into_bytes(self) -> Result { let (_parts, body) = self.into_parts(); - body.collect() - .await - .map(|c| c.to_bytes()) - .map_err(|e| ServerFnError::Deserialization(e.to_string())) + body.collect().await.map(|c| c.to_bytes()).map_err(|e| { + ServerFnErrorErr::Deserialization(e.to_string()).into() + }) } - async fn try_into_string(self) -> Result> { + async fn try_into_string(self) -> Result { let bytes = self.try_into_bytes().await?; - String::from_utf8(bytes.to_vec()) - .map_err(|e| ServerFnError::Deserialization(e.to_string())) + String::from_utf8(bytes.to_vec()).map_err(|e| { + ServerFnErrorErr::Deserialization(e.to_string()).into() + }) } fn try_into_stream( self, ) -> Result< impl Stream> + Send + 'static, - ServerFnError, + E, > { Ok(self.into_body().into_data_stream().map(|chunk| { - chunk.map_err(|e| ServerFnError::Deserialization(e.to_string())) + chunk.map_err(|e| { + ServerFnErrorErr::Deserialization(e.to_string()).into() + }) })) } } diff --git a/server_fn/src/response/actix.rs b/server_fn/src/response/actix.rs index 711268e717..8221877fd0 100644 --- a/server_fn/src/response/actix.rs +++ b/server_fn/src/response/actix.rs @@ -1,6 +1,6 @@ use super::Res; use crate::error::{ - ServerFnError, ServerFnErrorErr, ServerFnErrorSerde, SERVER_FN_ERROR_HEADER, + FromServerFnError, ServerFnErrorWrapper, SERVER_FN_ERROR_HEADER, }; use actix_web::{ http::{ @@ -13,10 +13,6 @@ use actix_web::{ use bytes::Bytes; use futures::{Stream, StreamExt}; use send_wrapper::SendWrapper; -use std::{ - fmt::{Debug, Display}, - str::FromStr, -}; /// A wrapped Actix response. /// @@ -38,14 +34,11 @@ impl From for ActixResponse { } } -impl Res for ActixResponse +impl Res for ActixResponse where - CustErr: FromStr + Display + Debug + 'static, + E: FromServerFnError, { - fn try_from_string( - content_type: &str, - data: String, - ) -> Result> { + fn try_from_string(content_type: &str, data: String) -> Result { let mut builder = HttpResponse::build(StatusCode::OK); Ok(ActixResponse(SendWrapper::new( builder @@ -54,10 +47,7 @@ where ))) } - fn try_from_bytes( - content_type: &str, - data: Bytes, - ) -> Result> { + fn try_from_bytes(content_type: &str, data: Bytes) -> Result { let mut builder = HttpResponse::build(StatusCode::OK); Ok(ActixResponse(SendWrapper::new( builder @@ -68,19 +58,17 @@ where fn try_from_stream( content_type: &str, - data: impl Stream>> + 'static, - ) -> Result> { + data: impl Stream> + 'static, + ) -> Result { let mut builder = HttpResponse::build(StatusCode::OK); Ok(ActixResponse(SendWrapper::new( builder .insert_header((header::CONTENT_TYPE, content_type)) - .streaming( - data.map(|data| data.map_err(ServerFnErrorErr::from)), - ), + .streaming(data.map(|data| data.map_err(ServerFnErrorWrapper))), ))) } - fn error_response(path: &str, err: &ServerFnError) -> Self { + fn error_response(path: &str, err: &E) -> Self { ActixResponse(SendWrapper::new( HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR) .append_header((SERVER_FN_ERROR_HEADER, path)) diff --git a/server_fn/src/response/browser.rs b/server_fn/src/response/browser.rs index 6c4cfcc1b5..f03750aaf8 100644 --- a/server_fn/src/response/browser.rs +++ b/server_fn/src/response/browser.rs @@ -1,5 +1,8 @@ use super::ClientRes; -use crate::{error::ServerFnError, redirect::REDIRECT_HEADER}; +use crate::{ + error::{FromServerFnError, ServerFnErrorErr}, + redirect::REDIRECT_HEADER, +}; use bytes::Bytes; use futures::{Stream, StreamExt}; pub use gloo_net::http::Response; @@ -12,48 +15,36 @@ use wasm_streams::ReadableStream; /// The response to a `fetch` request made in the browser. pub struct BrowserResponse(pub(crate) SendWrapper); -impl ClientRes for BrowserResponse { - fn try_into_string( - self, - ) -> impl Future>> + Send - { +impl ClientRes for BrowserResponse { + fn try_into_string(self) -> impl Future> + Send { // the browser won't send this async work between threads (because it's single-threaded) // so we can safely wrap this SendWrapper::new(async move { - self.0 - .text() - .await - .map_err(|e| ServerFnError::Deserialization(e.to_string())) + self.0.text().await.map_err(|e| { + ServerFnErrorErr::Deserialization(e.to_string()).into() + }) }) } - fn try_into_bytes( - self, - ) -> impl Future>> + Send - { + fn try_into_bytes(self) -> impl Future> + Send { // the browser won't send this async work between threads (because it's single-threaded) // so we can safely wrap this SendWrapper::new(async move { - self.0 - .binary() - .await - .map(Bytes::from) - .map_err(|e| ServerFnError::Deserialization(e.to_string())) + self.0.binary().await.map(Bytes::from).map_err(|e| { + ServerFnErrorErr::Deserialization(e.to_string()).into() + }) }) } fn try_into_stream( self, - ) -> Result< - impl Stream> + Send + 'static, - ServerFnError, - > { + ) -> Result> + Send + 'static, E> { let stream = ReadableStream::from_raw(self.0.body().unwrap()) .into_stream() .map(|data| match data { Err(e) => { web_sys::console::error_1(&e); - Err(ServerFnError::Request(format!("{e:?}"))) + Err(ServerFnErrorErr::Request(format!("{e:?}")).into()) } Ok(data) => { let data = data.unchecked_into::(); diff --git a/server_fn/src/response/generic.rs b/server_fn/src/response/generic.rs index f9e10b5f4c..644801481b 100644 --- a/server_fn/src/response/generic.rs +++ b/server_fn/src/response/generic.rs @@ -14,16 +14,13 @@ use super::Res; use crate::error::{ - ServerFnError, ServerFnErrorErr, ServerFnErrorSerde, SERVER_FN_ERROR_HEADER, + FromServerFnError, ServerFnErrorErr, ServerFnErrorWrapper, + SERVER_FN_ERROR_HEADER, }; use bytes::Bytes; use futures::{Stream, TryStreamExt}; use http::{header, HeaderValue, Response, StatusCode}; -use std::{ - fmt::{Debug, Display}, - pin::Pin, - str::FromStr, -}; +use std::pin::Pin; use throw_error::Error; /// The Body of a Response whose *execution model* can be @@ -44,51 +41,43 @@ impl From for Body { } } -impl Res for Response +impl Res for Response where - CustErr: Send + Sync + Debug + FromStr + Display + 'static, + E: Send + Sync + FromServerFnError, { - fn try_from_string( - content_type: &str, - data: String, - ) -> Result> { + fn try_from_string(content_type: &str, data: String) -> Result { let builder = http::Response::builder(); builder .status(200) .header(http::header::CONTENT_TYPE, content_type) .body(data.into()) - .map_err(|e| ServerFnError::Response(e.to_string())) + .map_err(|e| ServerFnErrorErr::Response(e.to_string()).into()) } - fn try_from_bytes( - content_type: &str, - data: Bytes, - ) -> Result> { + fn try_from_bytes(content_type: &str, data: Bytes) -> Result { let builder = http::Response::builder(); builder .status(200) .header(http::header::CONTENT_TYPE, content_type) .body(Body::Sync(data)) - .map_err(|e| ServerFnError::Response(e.to_string())) + .map_err(|e| ServerFnErrorErr::Response(e.to_string()).into()) } fn try_from_stream( content_type: &str, - data: impl Stream>> - + Send - + 'static, - ) -> Result> { + data: impl Stream> + Send + 'static, + ) -> Result { let builder = http::Response::builder(); builder .status(200) .header(http::header::CONTENT_TYPE, content_type) .body(Body::Async(Box::pin( - data.map_err(ServerFnErrorErr::from).map_err(Error::from), + data.map_err(ServerFnErrorWrapper).map_err(Error::from), ))) - .map_err(|e| ServerFnError::Response(e.to_string())) + .map_err(|e| ServerFnErrorErr::Response(e.to_string()).into()) } - fn error_response(path: &str, err: &ServerFnError) -> Self { + fn error_response(path: &str, err: &E) -> Self { Response::builder() .status(http::StatusCode::INTERNAL_SERVER_ERROR) .header(SERVER_FN_ERROR_HEADER, path) diff --git a/server_fn/src/response/http.rs b/server_fn/src/response/http.rs index e8117f75cc..254bf4e36b 100644 --- a/server_fn/src/response/http.rs +++ b/server_fn/src/response/http.rs @@ -1,61 +1,49 @@ use super::Res; use crate::error::{ - ServerFnError, ServerFnErrorErr, ServerFnErrorSerde, SERVER_FN_ERROR_HEADER, + FromServerFnError, ServerFnErrorErr, ServerFnErrorWrapper, + SERVER_FN_ERROR_HEADER, }; use axum::body::Body; use bytes::Bytes; -use futures::{Stream, StreamExt}; +use futures::{Stream, TryStreamExt}; use http::{header, HeaderValue, Response, StatusCode}; -use std::{ - fmt::{Debug, Display}, - str::FromStr, -}; -impl Res for Response +impl Res for Response where - CustErr: Send + Sync + Debug + FromStr + Display + 'static, + E: Send + Sync + FromServerFnError, { - fn try_from_string( - content_type: &str, - data: String, - ) -> Result> { + fn try_from_string(content_type: &str, data: String) -> Result { let builder = http::Response::builder(); builder .status(200) .header(http::header::CONTENT_TYPE, content_type) .body(Body::from(data)) - .map_err(|e| ServerFnError::Response(e.to_string())) + .map_err(|e| ServerFnErrorErr::Response(e.to_string()).into()) } - fn try_from_bytes( - content_type: &str, - data: Bytes, - ) -> Result> { + fn try_from_bytes(content_type: &str, data: Bytes) -> Result { let builder = http::Response::builder(); builder .status(200) .header(http::header::CONTENT_TYPE, content_type) .body(Body::from(data)) - .map_err(|e| ServerFnError::Response(e.to_string())) + .map_err(|e| ServerFnErrorErr::Response(e.to_string()).into()) } fn try_from_stream( content_type: &str, - data: impl Stream>> - + Send - + 'static, - ) -> Result> { - let body = - Body::from_stream(data.map(|n| n.map_err(ServerFnErrorErr::from))); + data: impl Stream> + Send + 'static, + ) -> Result { + let body = Body::from_stream(data.map_err(|e| ServerFnErrorWrapper(e))); let builder = http::Response::builder(); builder .status(200) .header(http::header::CONTENT_TYPE, content_type) .body(body) - .map_err(|e| ServerFnError::Response(e.to_string())) + .map_err(|e| E::from(ServerFnErrorErr::Response(e.to_string()))) } - fn error_response(path: &str, err: &ServerFnError) -> Self { + fn error_response(path: &str, err: &E) -> Self { Response::builder() .status(http::StatusCode::INTERNAL_SERVER_ERROR) .header(SERVER_FN_ERROR_HEADER, path) diff --git a/server_fn/src/response/mod.rs b/server_fn/src/response/mod.rs index 6a0f60bace..b9e212e1be 100644 --- a/server_fn/src/response/mod.rs +++ b/server_fn/src/response/mod.rs @@ -13,62 +13,46 @@ pub mod http; #[cfg(feature = "reqwest")] pub mod reqwest; -use crate::error::ServerFnError; use bytes::Bytes; use futures::Stream; use std::future::Future; /// Represents the response as created by the server; -pub trait Res +pub trait Res where Self: Sized, { /// Attempts to convert a UTF-8 string into an HTTP response. - fn try_from_string( - content_type: &str, - data: String, - ) -> Result>; + fn try_from_string(content_type: &str, data: String) -> Result; /// Attempts to convert a binary blob represented as bytes into an HTTP response. - fn try_from_bytes( - content_type: &str, - data: Bytes, - ) -> Result>; + fn try_from_bytes(content_type: &str, data: Bytes) -> Result; /// Attempts to convert a stream of bytes into an HTTP response. fn try_from_stream( content_type: &str, - data: impl Stream>> - + Send - + 'static, - ) -> Result>; + data: impl Stream> + Send + 'static, + ) -> Result; /// Converts an error into a response, with a `500` status code and the error text as its body. - fn error_response(path: &str, err: &ServerFnError) -> Self; + fn error_response(path: &str, err: &E) -> Self; /// Redirect the response by setting a 302 code and Location header. fn redirect(&mut self, path: &str); } /// Represents the response as received by the client. -pub trait ClientRes { +pub trait ClientRes { /// Attempts to extract a UTF-8 string from an HTTP response. - fn try_into_string( - self, - ) -> impl Future>> + Send; + fn try_into_string(self) -> impl Future> + Send; /// Attempts to extract a binary blob from an HTTP response. - fn try_into_bytes( - self, - ) -> impl Future>> + Send; + fn try_into_bytes(self) -> impl Future> + Send; /// Attempts to extract a binary stream from an HTTP response. fn try_into_stream( self, - ) -> Result< - impl Stream> + Send + Sync + 'static, - ServerFnError, - >; + ) -> Result> + Send + Sync + 'static, E>; /// HTTP status code of the response. fn status(&self) -> u16; @@ -91,29 +75,23 @@ pub trait ClientRes { /// server response type when compiling for the client. pub struct BrowserMockRes; -impl Res for BrowserMockRes { - fn try_from_string( - _content_type: &str, - _data: String, - ) -> Result> { +impl Res for BrowserMockRes { + fn try_from_string(_content_type: &str, _data: String) -> Result { unreachable!() } - fn try_from_bytes( - _content_type: &str, - _data: Bytes, - ) -> Result> { + fn try_from_bytes(_content_type: &str, _data: Bytes) -> Result { unreachable!() } - fn error_response(_path: &str, _err: &ServerFnError) -> Self { + fn error_response(_path: &str, _err: &E) -> Self { unreachable!() } fn try_from_stream( _content_type: &str, - _data: impl Stream>>, - ) -> Result> { + _data: impl Stream>, + ) -> Result { unreachable!() } diff --git a/server_fn/src/response/reqwest.rs b/server_fn/src/response/reqwest.rs index f60338e48d..79086ab0bf 100644 --- a/server_fn/src/response/reqwest.rs +++ b/server_fn/src/response/reqwest.rs @@ -1,31 +1,28 @@ use super::ClientRes; -use crate::error::ServerFnError; +use crate::error::{FromServerFnError, ServerFnErrorErr}; use bytes::Bytes; use futures::{Stream, TryStreamExt}; use reqwest::Response; -impl ClientRes for Response { - async fn try_into_string(self) -> Result> { - self.text() - .await - .map_err(|e| ServerFnError::Deserialization(e.to_string())) +impl ClientRes for Response { + async fn try_into_string(self) -> Result { + self.text().await.map_err(|e| { + ServerFnErrorErr::Deserialization(e.to_string()).into() + }) } - async fn try_into_bytes(self) -> Result> { - self.bytes() - .await - .map_err(|e| ServerFnError::Deserialization(e.to_string())) + async fn try_into_bytes(self) -> Result { + self.bytes().await.map_err(|e| { + ServerFnErrorErr::Deserialization(e.to_string()).into() + }) } fn try_into_stream( self, - ) -> Result< - impl Stream> + Send + 'static, - ServerFnError, - > { + ) -> Result> + Send + 'static, E> { Ok(self .bytes_stream() - .map_err(|e| ServerFnError::Response(e.to_string()))) + .map_err(|e| ServerFnErrorErr::Response(e.to_string()).into())) } fn status(&self) -> u16 { From 496b8203c4932721a8b268d083347e3fa8fdf06c Mon Sep 17 00:00:00 2001 From: Ryo Hirayama Date: Fri, 22 Nov 2024 10:02:35 +0900 Subject: [PATCH 02/15] Fix leptos crate --- leptos/src/form.rs | 13 ++++++++----- leptos/src/lib.rs | 2 +- server_fn/src/lib.rs | 1 + 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/leptos/src/form.rs b/leptos/src/form.rs index 648b9c9013..fb7f6b1989 100644 --- a/leptos/src/form.rs +++ b/leptos/src/form.rs @@ -3,7 +3,8 @@ 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::ServerFnErrorErr, + request::ClientReq, ServerFn, }; use tachys::{ either::Either, @@ -123,9 +124,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()))); version.update(|n| *n += 1); } } @@ -191,9 +193,10 @@ where action.dispatch(new_input); } Err(err) => { - action.dispatch_sync(Err(ServerFnError::Serialization( + action.dispatch_sync(Err(ServerFnErrorErr::Serialization( err.to_string(), - ))); + ) + .into())); } } }; diff --git a/leptos/src/lib.rs b/leptos/src/lib.rs index 0e40893d1f..5f8688b086 100644 --- a/leptos/src/lib.rs +++ b/leptos/src/lib.rs @@ -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::{ diff --git a/server_fn/src/lib.rs b/server_fn/src/lib.rs index bd2ba5afa5..9137310ac2 100644 --- a/server_fn/src/lib.rs +++ b/server_fn/src/lib.rs @@ -131,6 +131,7 @@ use codec::{Encoding, FromReq, FromRes, IntoReq, IntoRes}; #[doc(hidden)] pub use const_format; use dashmap::DashMap; +pub use error::ServerFnError; #[cfg(feature = "form-redirects")] use error::ServerFnUrlError; use error::{FromServerFnError, ServerFnErrorSerde}; From c0b3108660de9fefd85969badbc3d7e7961feae8 Mon Sep 17 00:00:00 2001 From: Ryo Hirayama Date: Fri, 22 Nov 2024 10:35:11 +0900 Subject: [PATCH 03/15] Allow custom error on server macro --- server_fn_macro/src/lib.rs | 31 +++++++------------------------ 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/server_fn_macro/src/lib.rs b/server_fn_macro/src/lib.rs index 1e80222a8e..b7e477fdce 100644 --- a/server_fn_macro/src/lib.rs +++ b/server_fn_macro/src/lib.rs @@ -716,12 +716,12 @@ fn output_type(return_ty: &Type) -> Result<&GenericArgument> { Err(syn::Error::new( return_ty.span(), - "server functions should return Result or Result>", + "server functions should return Result where E: \ + FromServerFnError", )) } -fn err_type(return_ty: &Type) -> Result> { +fn err_type(return_ty: &Type) -> Result> { if let syn::Type::Path(pat) = &return_ty { if pat.path.segments[0].ident == "Result" { if let PathArguments::AngleBracketed(args) = @@ -732,25 +732,8 @@ fn err_type(return_ty: &Type) -> Result> { return Ok(None); } // Result - else if let GenericArgument::Type(Type::Path(pat)) = - &args.args[1] - { - if let Some(segment) = pat.path.segments.last() { - if segment.ident == "ServerFnError" { - let args = &segment.arguments; - match args { - // Result - PathArguments::None => return Ok(None), - // Result> - PathArguments::AngleBracketed(args) => { - if args.args.len() == 1 { - return Ok(Some(&args.args[0])); - } - } - _ => {} - } - } - } + else if let GenericArgument::Type(ty) = &args.args[1] { + return Ok(Some(ty)); } } } @@ -758,8 +741,8 @@ fn err_type(return_ty: &Type) -> Result> { Err(syn::Error::new( return_ty.span(), - "server functions should return Result or Result>", + "server functions should return Result where E: \ + FromServerFnError", )) } From fd68c76091697e3998f6996b07bfcef485d664f4 Mon Sep 17 00:00:00 2001 From: Ryo Hirayama Date: Fri, 22 Nov 2024 13:00:40 +0900 Subject: [PATCH 04/15] Remove WrappedServerError variant --- examples/server_fns_axum/src/app.rs | 52 +++-- server_fn/src/codec/stream.rs | 6 +- server_fn/src/error.rs | 325 ++-------------------------- server_fn/src/middleware/mod.rs | 2 +- server_fn/src/request/axum.rs | 16 +- server_fn/src/response/actix.rs | 3 +- server_fn/src/response/generic.rs | 2 +- server_fn/src/response/http.rs | 4 +- 8 files changed, 74 insertions(+), 336 deletions(-) diff --git a/examples/server_fns_axum/src/app.rs b/examples/server_fns_axum/src/app.rs index 75f5c8fe79..e64917fd60 100644 --- a/examples/server_fns_axum/src/app.rs +++ b/examples/server_fns_axum/src/app.rs @@ -9,6 +9,7 @@ use server_fn::{ MultipartFormData, Postcard, Rkyv, SerdeLite, StreamingText, TextStream, }, + error::{FromServerFnError, ServerFnErrorErr}, request::{browser::BrowserRequest, ClientReq, Req}, response::{browser::BrowserResponse, ClientRes, Res}, }; @@ -18,7 +19,7 @@ use std::sync::{ atomic::{AtomicU8, Ordering}, Mutex, }; -use strum::{Display, EnumString}; +use strum::Display; use wasm_bindgen::JsCast; use web_sys::{FormData, HtmlFormElement, SubmitEvent}; @@ -652,9 +653,7 @@ 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> { +pub async fn ascii_uppercase(text: String) -> Result { if text.len() < 5 { Err(InvalidArgument::TooShort.into()) } else if text.len() > 15 { @@ -667,11 +666,18 @@ pub async fn ascii_uppercase( } // The EnumString and Display derive macros are provided by strum -#[derive(Debug, Clone, EnumString, Display)] +#[derive(Debug, Clone, Display, Serialize, Deserialize)] pub enum InvalidArgument { TooShort, TooLong, NotAscii, + ServerFnError(ServerFnErrorErr), +} + +impl From for InvalidArgument { + fn from(value: ServerFnErrorErr) -> Self { + InvalidArgument::ServerFnError(value) + } } #[component] @@ -726,14 +732,11 @@ impl IntoReq for TomlEncoded where Request: ClientReq, T: Serialize, + Err: FromServerFnError, { - fn into_req( - self, - path: &str, - accepts: &str, - ) -> Result> { + fn into_req(self, path: &str, accepts: &str) -> Result { let data = toml::to_string(&self.0) - .map_err(|e| ServerFnError::Serialization(e.to_string()))?; + .map_err(|e| ServerFnErrorErr::Serialization(e.to_string()))?; Request::try_new_post(path, Toml::CONTENT_TYPE, accepts, data) } } @@ -742,12 +745,13 @@ impl FromReq for TomlEncoded where Request: Req + Send, T: DeserializeOwned, + Err: FromServerFnError, { - async fn from_req(req: Request) -> Result> { + async fn from_req(req: Request) -> Result { let string_data = req.try_into_string().await?; toml::from_str::(&string_data) .map(TomlEncoded) - .map_err(|e| ServerFnError::Args(e.to_string())) + .map_err(|e| ServerFnErrorErr::Args(e.to_string()).into()) } } @@ -755,10 +759,11 @@ impl IntoRes for TomlEncoded where Response: Res, T: Serialize + Send, + Err: FromServerFnError, { - async fn into_res(self) -> Result> { + async fn into_res(self) -> Result { let data = toml::to_string(&self.0) - .map_err(|e| ServerFnError::Serialization(e.to_string()))?; + .map_err(|e| ServerFnErrorErr::Serialization(e.to_string()))?; Response::try_from_string(Toml::CONTENT_TYPE, data) } } @@ -767,12 +772,13 @@ impl FromRes for TomlEncoded where Response: ClientRes + Send, T: DeserializeOwned, + Err: FromServerFnError, { - async fn from_res(res: Response) -> Result> { + async fn from_res(res: Response) -> Result { 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() + }) } } @@ -835,7 +841,10 @@ pub fn CustomClientExample() -> impl IntoView { pub struct CustomClient; // Implement the `Client` trait for it. - impl Client for CustomClient { + impl Client 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; @@ -844,8 +853,7 @@ pub fn CustomClientExample() -> impl IntoView { // Our custom `send()` implementation does all the work. fn send( req: Self::Request, - ) -> impl Future>> - + Send { + ) -> impl Future> + Send { // BrowserRequest derefs to the underlying Request type from gloo-net, // so we can get access to the headers here let headers = req.headers(); diff --git a/server_fn/src/codec/stream.rs b/server_fn/src/codec/stream.rs index 95958f968a..4b77e31c79 100644 --- a/server_fn/src/codec/stream.rs +++ b/server_fn/src/codec/stream.rs @@ -3,7 +3,7 @@ use crate::{ error::{FromServerFnError, ServerFnErrorErr}, request::{ClientReq, Req}, response::{ClientRes, Res}, - IntoRes, + IntoRes, ServerFnError, }; use bytes::Bytes; use futures::{Stream, StreamExt}; @@ -151,7 +151,9 @@ impl Encoding for StreamingText { /// end before the output will begin. /// /// Streaming requests are only allowed over HTTP2 or HTTP3. -pub struct TextStream(Pin> + Send>>); +pub struct TextStream( + Pin> + Send>>, +); impl Debug for TextStream { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/server_fn/src/error.rs b/server_fn/src/error.rs index aa1781fb98..079e3ad3ae 100644 --- a/server_fn/src/error.rs +++ b/server_fn/src/error.rs @@ -2,11 +2,7 @@ use base64::{engine::general_purpose::URL_SAFE, Engine as _}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::{ - fmt, - fmt::{Display, Write}, - str::FromStr, -}; +use std::{fmt, fmt::Display}; use thiserror::Error; use throw_error::Error; use url::Url; @@ -16,143 +12,7 @@ pub const SERVER_FN_ERROR_HEADER: &str = "serverfnerror"; impl From for Error { fn from(e: ServerFnError) -> Self { - Error::from(ServerFnErrorErr::from(e)) - } -} - -/// An empty value indicating that there is no custom error type associated -/// with this server function. -#[derive( - Debug, - Deserialize, - Serialize, - PartialEq, - Eq, - Hash, - PartialOrd, - Ord, - Clone, - Copy, -)] -#[cfg_attr( - feature = "rkyv", - derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) -)] -#[deprecated( - since = "0.8.0", - note = "Now server_fn can return any error type other than ServerFnError, \ - so the WrappedServerError variant will be removed in 0.9.0" -)] -pub struct NoCustomError; - -// Implement `Display` for `NoCustomError` -impl fmt::Display for NoCustomError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Unit Type Displayed") - } -} - -impl FromStr for NoCustomError { - type Err = (); - - fn from_str(_s: &str) -> Result { - Ok(NoCustomError) - } -} - -/// Wraps some error type, which may implement any of [`Error`](trait@std::error::Error), [`Clone`], or -/// [`Display`]. -#[derive(Debug)] -#[deprecated( - since = "0.8.0", - note = "Now server_fn can return any error type other than ServerFnError, \ - so the WrappedServerError variant will be removed in 0.9.0" -)] -pub struct WrapError(pub T); - -/// A helper macro to convert a variety of different types into `ServerFnError`. -/// This should mostly be used if you are implementing `From` for `YourError`. -#[macro_export] -#[deprecated( - since = "0.8.0", - note = "Now server_fn can return any error type other than ServerFnError, \ - so the WrappedServerError variant will be removed in 0.9.0" -)] -macro_rules! server_fn_error { - () => {{ - use $crate::{ViaError, WrapError}; - (&&&&&WrapError(())).to_server_error() - }}; - ($err:expr) => {{ - use $crate::error::{ViaError, WrapError}; - match $err { - error => (&&&&&WrapError(error)).to_server_error(), - } - }}; -} - -/// This trait serves as the conversion method between a variety of types -/// and [`ServerFnError`]. -#[deprecated( - since = "0.8.0", - note = "Now server_fn can return any error type other than ServerFnError, \ - so users should place their custom error type instead of \ - ServerFnError" -)] -pub trait ViaError { - /// Converts something into an error. - fn to_server_error(&self) -> ServerFnError; -} - -// This impl should catch if you fed it a [`ServerFnError`] already. -impl ViaError - for &&&&WrapError> -{ - fn to_server_error(&self) -> ServerFnError { - self.0.clone() - } -} - -// A type tag for ServerFnError so we can special case it -#[deprecated] -pub(crate) trait ServerFnErrorKind {} - -impl ServerFnErrorKind for ServerFnError {} - -// This impl should catch passing () or nothing to server_fn_error -impl ViaError for &&&WrapError<()> { - fn to_server_error(&self) -> ServerFnError { - ServerFnError::WrappedServerError(NoCustomError) - } -} - -// This impl will catch any type that implements any type that impls -// Error and Clone, so that it can be wrapped into ServerFnError -impl ViaError for &&WrapError { - fn to_server_error(&self) -> ServerFnError { - ServerFnError::WrappedServerError(self.0.clone()) - } -} - -// If it doesn't impl Error, but does impl Display and Clone, -// we can still wrap it in String form -impl ViaError for &WrapError { - fn to_server_error(&self) -> ServerFnError { - ServerFnError::ServerError(self.0.to_string()) - } -} - -// This is what happens if someone tries to pass in something that does -// not meet the above criteria -impl ViaError for WrapError { - #[track_caller] - fn to_server_error(&self) -> ServerFnError { - panic!( - "At {}, you call `to_server_error()` or use `server_fn_error!` \ - with a value that does not implement `Clone` and either `Error` \ - or `Display`.", - std::panic::Location::caller() - ); + Error::from(ServerFnErrorWrapper(e)) } } @@ -167,15 +27,7 @@ impl ViaError for WrapError { feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) )] -pub enum ServerFnError { - #[deprecated( - since = "0.8.0", - note = "Now server_fn can return any error type other than \ - ServerFnError, so users should place their custom error type \ - instead of ServerFnError" - )] - /// A user-defined custom error type, which defaults to [`NoCustomError`]. - WrappedServerError(E), +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. @@ -196,29 +48,20 @@ pub enum ServerFnError { MissingArg(String), } -impl ServerFnError { +impl ServerFnError { /// Constructs a new [`ServerFnError::ServerError`] from some other type. pub fn new(msg: impl ToString) -> Self { Self::ServerError(msg.to_string()) } } -impl From for ServerFnError { - fn from(value: CustErr) -> Self { - ServerFnError::WrappedServerError(value) - } -} - impl From for ServerFnError { fn from(value: E) -> Self { ServerFnError::ServerError(value.to_string()) } } -impl Display for ServerFnError -where - CustErr: Display, -{ +impl Display for ServerFnError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, @@ -244,7 +87,6 @@ where ServerFnError::MissingArg(s) => format!("missing argument {s}"), ServerFnError::Response(s) => format!("error generating HTTP response: {s}"), - ServerFnError::WrappedServerError(e) => format!("{e}"), } ) } @@ -255,7 +97,7 @@ where /// This is implemented for all types that implement [`FromStr`] + [`Display`]. /// /// This means you do not necessarily need the overhead of `serde` for a custom error type. -/// Instead, you can use something like `strum` to derive `FromStr` and `Display` for your +/// Instead, you can use something lie `strum` to derive `FromStr` and `Display` for your /// custom error type. /// /// This is implemented for the default [`ServerFnError`], which uses [`NoCustomError`]. @@ -270,93 +112,6 @@ pub trait ServerFnErrorSerde: Sized { fn de(data: &str) -> Self; } -impl ServerFnErrorSerde for ServerFnErrorErr -where - CustErr: FromStr + Display, -{ - type Error = std::fmt::Error; - - fn ser(&self) -> Result { - let mut buf = String::new(); - match self { - ServerFnErrorErr::WrappedServerError(e) => { - write!(&mut buf, "WrappedServerFn|{e}") - } - ServerFnErrorErr::Registration(e) => { - write!(&mut buf, "Registration|{e}") - } - ServerFnErrorErr::Request(e) => write!(&mut buf, "Request|{e}"), - ServerFnErrorErr::Response(e) => write!(&mut buf, "Response|{e}"), - ServerFnErrorErr::ServerError(e) => { - write!(&mut buf, "ServerError|{e}") - } - ServerFnErrorErr::MiddlewareError(e) => { - write!(&mut buf, "MiddlewareError|{e}") - } - ServerFnErrorErr::Deserialization(e) => { - write!(&mut buf, "Deserialization|{e}") - } - ServerFnErrorErr::Serialization(e) => { - write!(&mut buf, "Serialization|{e}") - } - ServerFnErrorErr::Args(e) => write!(&mut buf, "Args|{e}"), - ServerFnErrorErr::MissingArg(e) => { - write!(&mut buf, "MissingArg|{e}") - } - }?; - Ok(buf) - } - - fn de(data: &str) -> Self { - data.split_once('|') - .and_then(|(ty, data)| match ty { - "WrappedServerFn" => match CustErr::from_str(data) { - Ok(d) => Some(ServerFnErrorErr::WrappedServerError(d)), - Err(_) => None, - }, - "Registration" => { - Some(ServerFnErrorErr::Registration(data.to_string())) - } - "Request" => Some(ServerFnErrorErr::Request(data.to_string())), - "Response" => { - Some(ServerFnErrorErr::Response(data.to_string())) - } - "ServerError" => { - Some(ServerFnErrorErr::ServerError(data.to_string())) - } - "Deserialization" => { - Some(ServerFnErrorErr::Deserialization(data.to_string())) - } - "Serialization" => { - Some(ServerFnErrorErr::Serialization(data.to_string())) - } - "Args" => Some(ServerFnErrorErr::Args(data.to_string())), - "MissingArg" => { - Some(ServerFnErrorErr::MissingArg(data.to_string())) - } - _ => None, - }) - .unwrap_or_else(|| { - ServerFnErrorErr::Deserialization(format!( - "Could not deserialize error {data:?}" - )) - }) - } -} - -impl std::error::Error for ServerFnError -where - E: std::error::Error + 'static, - ServerFnError: std::fmt::Display, -{ - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - ServerFnError::WrappedServerError(e) => Some(e), - _ => None, - } - } -} - /// Type for errors that can occur when using server functions. If you need to return a custom error type from a server function, implement `From` for your custom error type. /// /// Unlike [`ServerFnError`], this implements [`std::error::Error`]. This means @@ -366,11 +121,12 @@ where /// /// [`ServerFnError`] and [`ServerFnErrorErr`] mutually implement [`From`], so /// it is easy to convert between the two types. -#[derive(Error, Debug, Clone, PartialEq, Eq)] -pub enum ServerFnErrorErr { - /// A user-defined custom error type, which defaults to [`NoCustomError`]. - #[error("internal error: {0}")] - WrappedServerError(E), +#[derive(Error, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Serialize, rkyv::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), @@ -400,37 +156,6 @@ pub enum ServerFnErrorErr { Response(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::MiddlewareError(value) => { - ServerFnErrorErr::MiddlewareError(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) - } - ServerFnError::WrappedServerError(value) => { - ServerFnErrorErr::WrappedServerError(value) - } - ServerFnError::Response(value) => ServerFnErrorErr::Response(value), - } - } -} - /// Associates a particular server function error with the server function /// found at a particular path. /// @@ -518,18 +243,6 @@ impl ServerFnUrlError { } } -impl From> for ServerFnError { - fn from(error: ServerFnUrlError) -> Self { - error.error.into() - } -} - -impl From>> for ServerFnError { - fn from(error: ServerFnUrlError>) -> Self { - error.error - } -} - #[derive(Debug)] #[doc(hidden)] /// Only used instantly only when a framework needs E: Error. @@ -551,8 +264,10 @@ impl std::error::Error for ServerFnErrorWrapper { pub trait FromServerFnError: Display + std::fmt::Debug - + ServerFnErrorSerde + From + + Serialize + + DeserializeOwned + + for<'a> Deserialize<'a> + 'static { } @@ -567,15 +282,17 @@ fn assert_from_server_fn_error_impl() { impl FromServerFnError for E where E: Display + std::fmt::Debug - + ServerFnErrorSerde + From + + Serialize + + DeserializeOwned + + for<'a> Deserialize<'a> + 'static { } impl ServerFnErrorSerde for E where - E: Serialize + DeserializeOwned, + E: FromServerFnError, { type Error = serde_json::Error; @@ -584,6 +301,8 @@ where } fn de(data: &str) -> Self { - serde_json::from_str(data).unwrap() + serde_json::from_str(data).unwrap_or_else(|e| { + ServerFnErrorErr::Deserialization(e.to_string()).into() + }) } } diff --git a/server_fn/src/middleware/mod.rs b/server_fn/src/middleware/mod.rs index dba6d4c204..b1171e4659 100644 --- a/server_fn/src/middleware/mod.rs +++ b/server_fn/src/middleware/mod.rs @@ -64,7 +64,7 @@ mod axum { for BoxedService, Response> { type Response = Response; - type Error = ServerFnErrorErr; + type Error = crate::ServerFnError; type Future = Pin< Box< dyn std::future::Future< diff --git a/server_fn/src/request/axum.rs b/server_fn/src/request/axum.rs index 1ae63c3f1b..2dea0c3864 100644 --- a/server_fn/src/request/axum.rs +++ b/server_fn/src/request/axum.rs @@ -41,14 +41,22 @@ where let (_parts, body) = self.into_parts(); body.collect().await.map(|c| c.to_bytes()).map_err(|e| { - ServerFnErrorErr::Deserialization(e.to_string()).into() + E::from(ServerFnErrorErr::Deserialization(e.to_string())) }) } async fn try_into_string(self) -> Result { - let bytes = self.try_into_bytes().await?; + // from: + let (_parts, body) = self.into_parts(); + + let bytes = + body.collect().await.map(|c| c.to_bytes()).map_err(|e| { + E::from(ServerFnErrorErr::Deserialization(e.to_string())) + })?; + // here: is the same as try_into_bytes, but rustc complains if `self.try_into_bytes()` called. + String::from_utf8(bytes.to_vec()).map_err(|e| { - ServerFnErrorErr::Deserialization(e.to_string()).into() + E::from(ServerFnErrorErr::Deserialization(e.to_string())) }) } @@ -57,7 +65,7 @@ where ) -> Result> + Send + 'static, E> { Ok(self.into_body().into_data_stream().map(|chunk| { chunk.map_err(|e| { - ServerFnErrorErr::Deserialization(e.to_string()).into() + E::from(ServerFnErrorErr::Deserialization(e.to_string())) }) })) } diff --git a/server_fn/src/response/actix.rs b/server_fn/src/response/actix.rs index 8221877fd0..443436f29e 100644 --- a/server_fn/src/response/actix.rs +++ b/server_fn/src/response/actix.rs @@ -1,6 +1,7 @@ use super::Res; use crate::error::{ - FromServerFnError, ServerFnErrorWrapper, SERVER_FN_ERROR_HEADER, + FromServerFnError, ServerFnErrorSerde, ServerFnErrorWrapper, + SERVER_FN_ERROR_HEADER, }; use actix_web::{ http::{ diff --git a/server_fn/src/response/generic.rs b/server_fn/src/response/generic.rs index 644801481b..a0e385607a 100644 --- a/server_fn/src/response/generic.rs +++ b/server_fn/src/response/generic.rs @@ -14,7 +14,7 @@ use super::Res; use crate::error::{ - FromServerFnError, ServerFnErrorErr, ServerFnErrorWrapper, + FromServerFnError, ServerFnErrorErr, ServerFnErrorSerde, ServerFnErrorWrapper, SERVER_FN_ERROR_HEADER, }; use bytes::Bytes; diff --git a/server_fn/src/response/http.rs b/server_fn/src/response/http.rs index 254bf4e36b..e2cf580e4b 100644 --- a/server_fn/src/response/http.rs +++ b/server_fn/src/response/http.rs @@ -1,7 +1,7 @@ use super::Res; use crate::error::{ - FromServerFnError, ServerFnErrorErr, ServerFnErrorWrapper, - SERVER_FN_ERROR_HEADER, + FromServerFnError, ServerFnErrorErr, ServerFnErrorSerde, + ServerFnErrorWrapper, SERVER_FN_ERROR_HEADER, }; use axum::body::Body; use bytes::Bytes; From 107581edd98a7e8b138f8e238a0c8d8fe65c3fc9 Mon Sep 17 00:00:00 2001 From: Ryo Hirayama Date: Fri, 22 Nov 2024 13:07:31 +0900 Subject: [PATCH 05/15] Fix ServerFnErrorSerde doc --- server_fn/src/error.rs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/server_fn/src/error.rs b/server_fn/src/error.rs index 079e3ad3ae..f4451d575d 100644 --- a/server_fn/src/error.rs +++ b/server_fn/src/error.rs @@ -1,8 +1,6 @@ -#![allow(deprecated)] - use base64::{engine::general_purpose::URL_SAFE, Engine as _}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::{fmt, fmt::Display}; +use std::fmt::{self, Display}; use thiserror::Error; use throw_error::Error; use url::Url; @@ -92,15 +90,8 @@ impl Display for ServerFnError { } } -/// A serializable custom server function error type. -/// -/// This is implemented for all types that implement [`FromStr`] + [`Display`]. -/// -/// This means you do not necessarily need the overhead of `serde` for a custom error type. -/// Instead, you can use something lie `strum` to derive `FromStr` and `Display` for your -/// custom error type. -/// -/// This is implemented for the default [`ServerFnError`], which uses [`NoCustomError`]. +#[doc(hidden)] +/// An extension trait for types that can be serialized and deserialized to a [`String`]. pub trait ServerFnErrorSerde: Sized { /// The error type that can occur when serializing the custom error type. type Error: std::error::Error; From 3dbd7054c348c581669e3d08819b6f1de0ab5c59 Mon Sep 17 00:00:00 2001 From: Ryo Hirayama Date: Fri, 22 Nov 2024 13:32:50 +0900 Subject: [PATCH 06/15] Revert "Remove WrappedServerError variant" This reverts commit fd68c76091697e3998f6996b07bfcef485d664f4. --- examples/server_fns_axum/src/app.rs | 52 ++--- server_fn/src/codec/stream.rs | 6 +- server_fn/src/error.rs | 325 ++++++++++++++++++++++++++-- server_fn/src/middleware/mod.rs | 2 +- server_fn/src/request/axum.rs | 16 +- server_fn/src/response/actix.rs | 3 +- server_fn/src/response/generic.rs | 2 +- server_fn/src/response/http.rs | 4 +- 8 files changed, 337 insertions(+), 73 deletions(-) diff --git a/examples/server_fns_axum/src/app.rs b/examples/server_fns_axum/src/app.rs index e64917fd60..75f5c8fe79 100644 --- a/examples/server_fns_axum/src/app.rs +++ b/examples/server_fns_axum/src/app.rs @@ -9,7 +9,6 @@ use server_fn::{ MultipartFormData, Postcard, Rkyv, SerdeLite, StreamingText, TextStream, }, - error::{FromServerFnError, ServerFnErrorErr}, request::{browser::BrowserRequest, ClientReq, Req}, response::{browser::BrowserResponse, ClientRes, Res}, }; @@ -19,7 +18,7 @@ use std::sync::{ atomic::{AtomicU8, Ordering}, Mutex, }; -use strum::Display; +use strum::{Display, EnumString}; use wasm_bindgen::JsCast; use web_sys::{FormData, HtmlFormElement, SubmitEvent}; @@ -653,7 +652,9 @@ 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 { +pub async fn ascii_uppercase( + text: String, +) -> Result> { if text.len() < 5 { Err(InvalidArgument::TooShort.into()) } else if text.len() > 15 { @@ -666,18 +667,11 @@ pub async fn ascii_uppercase(text: String) -> Result { } // The EnumString and Display derive macros are provided by strum -#[derive(Debug, Clone, Display, Serialize, Deserialize)] +#[derive(Debug, Clone, EnumString, Display)] pub enum InvalidArgument { TooShort, TooLong, NotAscii, - ServerFnError(ServerFnErrorErr), -} - -impl From for InvalidArgument { - fn from(value: ServerFnErrorErr) -> Self { - InvalidArgument::ServerFnError(value) - } } #[component] @@ -732,11 +726,14 @@ impl IntoReq for TomlEncoded where Request: ClientReq, T: Serialize, - Err: FromServerFnError, { - fn into_req(self, path: &str, accepts: &str) -> Result { + fn into_req( + self, + path: &str, + accepts: &str, + ) -> Result> { let data = toml::to_string(&self.0) - .map_err(|e| ServerFnErrorErr::Serialization(e.to_string()))?; + .map_err(|e| ServerFnError::Serialization(e.to_string()))?; Request::try_new_post(path, Toml::CONTENT_TYPE, accepts, data) } } @@ -745,13 +742,12 @@ impl FromReq for TomlEncoded where Request: Req + Send, T: DeserializeOwned, - Err: FromServerFnError, { - async fn from_req(req: Request) -> Result { + async fn from_req(req: Request) -> Result> { let string_data = req.try_into_string().await?; toml::from_str::(&string_data) .map(TomlEncoded) - .map_err(|e| ServerFnErrorErr::Args(e.to_string()).into()) + .map_err(|e| ServerFnError::Args(e.to_string())) } } @@ -759,11 +755,10 @@ impl IntoRes for TomlEncoded where Response: Res, T: Serialize + Send, - Err: FromServerFnError, { - async fn into_res(self) -> Result { + async fn into_res(self) -> Result> { let data = toml::to_string(&self.0) - .map_err(|e| ServerFnErrorErr::Serialization(e.to_string()))?; + .map_err(|e| ServerFnError::Serialization(e.to_string()))?; Response::try_from_string(Toml::CONTENT_TYPE, data) } } @@ -772,13 +767,12 @@ impl FromRes for TomlEncoded where Response: ClientRes + Send, T: DeserializeOwned, - Err: FromServerFnError, { - async fn from_res(res: Response) -> Result { + async fn from_res(res: Response) -> Result> { let data = res.try_into_string().await?; - toml::from_str(&data).map(TomlEncoded).map_err(|e| { - ServerFnErrorErr::Deserialization(e.to_string()).into() - }) + toml::from_str(&data) + .map(TomlEncoded) + .map_err(|e| ServerFnError::Deserialization(e.to_string())) } } @@ -841,10 +835,7 @@ pub fn CustomClientExample() -> impl IntoView { pub struct CustomClient; // Implement the `Client` trait for it. - impl Client for CustomClient - where - E: FromServerFnError, - { + impl Client for CustomClient { // BrowserRequest and BrowserResponse are the defaults used by other server functions. // They are wrappers for the underlying Web Fetch API types. type Request = BrowserRequest; @@ -853,7 +844,8 @@ pub fn CustomClientExample() -> impl IntoView { // Our custom `send()` implementation does all the work. fn send( req: Self::Request, - ) -> impl Future> + Send { + ) -> impl Future>> + + Send { // BrowserRequest derefs to the underlying Request type from gloo-net, // so we can get access to the headers here let headers = req.headers(); diff --git a/server_fn/src/codec/stream.rs b/server_fn/src/codec/stream.rs index 4b77e31c79..95958f968a 100644 --- a/server_fn/src/codec/stream.rs +++ b/server_fn/src/codec/stream.rs @@ -3,7 +3,7 @@ use crate::{ error::{FromServerFnError, ServerFnErrorErr}, request::{ClientReq, Req}, response::{ClientRes, Res}, - IntoRes, ServerFnError, + IntoRes, }; use bytes::Bytes; use futures::{Stream, StreamExt}; @@ -151,9 +151,7 @@ impl Encoding for StreamingText { /// end before the output will begin. /// /// Streaming requests are only allowed over HTTP2 or HTTP3. -pub struct TextStream( - Pin> + Send>>, -); +pub struct TextStream(Pin> + Send>>); impl Debug for TextStream { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/server_fn/src/error.rs b/server_fn/src/error.rs index f4451d575d..6f6fa8e24e 100644 --- a/server_fn/src/error.rs +++ b/server_fn/src/error.rs @@ -1,6 +1,12 @@ +#![allow(deprecated)] + use base64::{engine::general_purpose::URL_SAFE, Engine as _}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::fmt::{self, Display}; +use std::{ + fmt, + fmt::{Display, Write}, + str::FromStr, +}; use thiserror::Error; use throw_error::Error; use url::Url; @@ -10,7 +16,143 @@ pub const SERVER_FN_ERROR_HEADER: &str = "serverfnerror"; impl From for Error { fn from(e: ServerFnError) -> Self { - Error::from(ServerFnErrorWrapper(e)) + Error::from(ServerFnErrorErr::from(e)) + } +} + +/// An empty value indicating that there is no custom error type associated +/// with this server function. +#[derive( + Debug, + Deserialize, + Serialize, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + Clone, + Copy, +)] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) +)] +#[deprecated( + since = "0.8.0", + note = "Now server_fn can return any error type other than ServerFnError, \ + so the WrappedServerError variant will be removed in 0.9.0" +)] +pub struct NoCustomError; + +// Implement `Display` for `NoCustomError` +impl fmt::Display for NoCustomError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Unit Type Displayed") + } +} + +impl FromStr for NoCustomError { + type Err = (); + + fn from_str(_s: &str) -> Result { + Ok(NoCustomError) + } +} + +/// Wraps some error type, which may implement any of [`Error`](trait@std::error::Error), [`Clone`], or +/// [`Display`]. +#[derive(Debug)] +#[deprecated( + since = "0.8.0", + note = "Now server_fn can return any error type other than ServerFnError, \ + so the WrappedServerError variant will be removed in 0.9.0" +)] +pub struct WrapError(pub T); + +/// A helper macro to convert a variety of different types into `ServerFnError`. +/// This should mostly be used if you are implementing `From` for `YourError`. +#[macro_export] +#[deprecated( + since = "0.8.0", + note = "Now server_fn can return any error type other than ServerFnError, \ + so the WrappedServerError variant will be removed in 0.9.0" +)] +macro_rules! server_fn_error { + () => {{ + use $crate::{ViaError, WrapError}; + (&&&&&WrapError(())).to_server_error() + }}; + ($err:expr) => {{ + use $crate::error::{ViaError, WrapError}; + match $err { + error => (&&&&&WrapError(error)).to_server_error(), + } + }}; +} + +/// This trait serves as the conversion method between a variety of types +/// and [`ServerFnError`]. +#[deprecated( + since = "0.8.0", + note = "Now server_fn can return any error type other than ServerFnError, \ + so users should place their custom error type instead of \ + ServerFnError" +)] +pub trait ViaError { + /// Converts something into an error. + fn to_server_error(&self) -> ServerFnError; +} + +// This impl should catch if you fed it a [`ServerFnError`] already. +impl ViaError + for &&&&WrapError> +{ + fn to_server_error(&self) -> ServerFnError { + self.0.clone() + } +} + +// A type tag for ServerFnError so we can special case it +#[deprecated] +pub(crate) trait ServerFnErrorKind {} + +impl ServerFnErrorKind for ServerFnError {} + +// This impl should catch passing () or nothing to server_fn_error +impl ViaError for &&&WrapError<()> { + fn to_server_error(&self) -> ServerFnError { + ServerFnError::WrappedServerError(NoCustomError) + } +} + +// This impl will catch any type that implements any type that impls +// Error and Clone, so that it can be wrapped into ServerFnError +impl ViaError for &&WrapError { + fn to_server_error(&self) -> ServerFnError { + ServerFnError::WrappedServerError(self.0.clone()) + } +} + +// If it doesn't impl Error, but does impl Display and Clone, +// we can still wrap it in String form +impl ViaError for &WrapError { + fn to_server_error(&self) -> ServerFnError { + ServerFnError::ServerError(self.0.to_string()) + } +} + +// This is what happens if someone tries to pass in something that does +// not meet the above criteria +impl ViaError for WrapError { + #[track_caller] + fn to_server_error(&self) -> ServerFnError { + panic!( + "At {}, you call `to_server_error()` or use `server_fn_error!` \ + with a value that does not implement `Clone` and either `Error` \ + or `Display`.", + std::panic::Location::caller() + ); } } @@ -25,7 +167,15 @@ impl From for Error { feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) )] -pub enum ServerFnError { +pub enum ServerFnError { + #[deprecated( + since = "0.8.0", + note = "Now server_fn can return any error type other than \ + ServerFnError, so users should place their custom error type \ + instead of ServerFnError" + )] + /// A user-defined custom error type, which defaults to [`NoCustomError`]. + WrappedServerError(E), /// 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. @@ -46,20 +196,29 @@ pub enum ServerFnError { MissingArg(String), } -impl ServerFnError { +impl ServerFnError { /// Constructs a new [`ServerFnError::ServerError`] from some other type. pub fn new(msg: impl ToString) -> Self { Self::ServerError(msg.to_string()) } } +impl From for ServerFnError { + fn from(value: CustErr) -> Self { + ServerFnError::WrappedServerError(value) + } +} + impl From for ServerFnError { fn from(value: E) -> Self { ServerFnError::ServerError(value.to_string()) } } -impl Display for ServerFnError { +impl Display for ServerFnError +where + CustErr: Display, +{ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, @@ -85,6 +244,7 @@ impl Display for ServerFnError { ServerFnError::MissingArg(s) => format!("missing argument {s}"), ServerFnError::Response(s) => format!("error generating HTTP response: {s}"), + ServerFnError::WrappedServerError(e) => format!("{e}"), } ) } @@ -103,6 +263,93 @@ pub trait ServerFnErrorSerde: Sized { fn de(data: &str) -> Self; } +impl ServerFnErrorSerde for ServerFnErrorErr +where + CustErr: FromStr + Display, +{ + type Error = std::fmt::Error; + + fn ser(&self) -> Result { + let mut buf = String::new(); + match self { + ServerFnErrorErr::WrappedServerError(e) => { + write!(&mut buf, "WrappedServerFn|{e}") + } + ServerFnErrorErr::Registration(e) => { + write!(&mut buf, "Registration|{e}") + } + ServerFnErrorErr::Request(e) => write!(&mut buf, "Request|{e}"), + ServerFnErrorErr::Response(e) => write!(&mut buf, "Response|{e}"), + ServerFnErrorErr::ServerError(e) => { + write!(&mut buf, "ServerError|{e}") + } + ServerFnErrorErr::MiddlewareError(e) => { + write!(&mut buf, "MiddlewareError|{e}") + } + ServerFnErrorErr::Deserialization(e) => { + write!(&mut buf, "Deserialization|{e}") + } + ServerFnErrorErr::Serialization(e) => { + write!(&mut buf, "Serialization|{e}") + } + ServerFnErrorErr::Args(e) => write!(&mut buf, "Args|{e}"), + ServerFnErrorErr::MissingArg(e) => { + write!(&mut buf, "MissingArg|{e}") + } + }?; + Ok(buf) + } + + fn de(data: &str) -> Self { + data.split_once('|') + .and_then(|(ty, data)| match ty { + "WrappedServerFn" => match CustErr::from_str(data) { + Ok(d) => Some(ServerFnErrorErr::WrappedServerError(d)), + Err(_) => None, + }, + "Registration" => { + Some(ServerFnErrorErr::Registration(data.to_string())) + } + "Request" => Some(ServerFnErrorErr::Request(data.to_string())), + "Response" => { + Some(ServerFnErrorErr::Response(data.to_string())) + } + "ServerError" => { + Some(ServerFnErrorErr::ServerError(data.to_string())) + } + "Deserialization" => { + Some(ServerFnErrorErr::Deserialization(data.to_string())) + } + "Serialization" => { + Some(ServerFnErrorErr::Serialization(data.to_string())) + } + "Args" => Some(ServerFnErrorErr::Args(data.to_string())), + "MissingArg" => { + Some(ServerFnErrorErr::MissingArg(data.to_string())) + } + _ => None, + }) + .unwrap_or_else(|| { + ServerFnErrorErr::Deserialization(format!( + "Could not deserialize error {data:?}" + )) + }) + } +} + +impl std::error::Error for ServerFnError +where + E: std::error::Error + 'static, + ServerFnError: std::fmt::Display, +{ + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + ServerFnError::WrappedServerError(e) => Some(e), + _ => None, + } + } +} + /// Type for errors that can occur when using server functions. If you need to return a custom error type from a server function, implement `From` for your custom error type. /// /// Unlike [`ServerFnError`], this implements [`std::error::Error`]. This means @@ -112,12 +359,11 @@ pub trait ServerFnErrorSerde: Sized { /// /// [`ServerFnError`] and [`ServerFnErrorErr`] mutually implement [`From`], so /// it is easy to convert between the two types. -#[derive(Error, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[cfg_attr( - feature = "rkyv", - derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) -)] -pub enum ServerFnErrorErr { +#[derive(Error, Debug, Clone, PartialEq, Eq)] +pub enum ServerFnErrorErr { + /// A user-defined custom error type, which defaults to [`NoCustomError`]. + #[error("internal error: {0}")] + WrappedServerError(E), /// 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), @@ -147,6 +393,37 @@ pub enum ServerFnErrorErr { Response(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::MiddlewareError(value) => { + ServerFnErrorErr::MiddlewareError(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) + } + ServerFnError::WrappedServerError(value) => { + ServerFnErrorErr::WrappedServerError(value) + } + ServerFnError::Response(value) => ServerFnErrorErr::Response(value), + } + } +} + /// Associates a particular server function error with the server function /// found at a particular path. /// @@ -234,6 +511,18 @@ impl ServerFnUrlError { } } +impl From> for ServerFnError { + fn from(error: ServerFnUrlError) -> Self { + error.error.into() + } +} + +impl From>> for ServerFnError { + fn from(error: ServerFnUrlError>) -> Self { + error.error + } +} + #[derive(Debug)] #[doc(hidden)] /// Only used instantly only when a framework needs E: Error. @@ -255,10 +544,8 @@ impl std::error::Error for ServerFnErrorWrapper { pub trait FromServerFnError: Display + std::fmt::Debug + + ServerFnErrorSerde + From - + Serialize - + DeserializeOwned - + for<'a> Deserialize<'a> + 'static { } @@ -273,17 +560,15 @@ fn assert_from_server_fn_error_impl() { impl FromServerFnError for E where E: Display + std::fmt::Debug + + ServerFnErrorSerde + From - + Serialize - + DeserializeOwned - + for<'a> Deserialize<'a> + 'static { } impl ServerFnErrorSerde for E where - E: FromServerFnError, + E: Serialize + DeserializeOwned, { type Error = serde_json::Error; @@ -292,8 +577,6 @@ where } fn de(data: &str) -> Self { - serde_json::from_str(data).unwrap_or_else(|e| { - ServerFnErrorErr::Deserialization(e.to_string()).into() - }) + serde_json::from_str(data).unwrap() } } diff --git a/server_fn/src/middleware/mod.rs b/server_fn/src/middleware/mod.rs index b1171e4659..dba6d4c204 100644 --- a/server_fn/src/middleware/mod.rs +++ b/server_fn/src/middleware/mod.rs @@ -64,7 +64,7 @@ mod axum { for BoxedService, Response> { type Response = Response; - type Error = crate::ServerFnError; + type Error = ServerFnErrorErr; type Future = Pin< Box< dyn std::future::Future< diff --git a/server_fn/src/request/axum.rs b/server_fn/src/request/axum.rs index 2dea0c3864..1ae63c3f1b 100644 --- a/server_fn/src/request/axum.rs +++ b/server_fn/src/request/axum.rs @@ -41,22 +41,14 @@ where let (_parts, body) = self.into_parts(); body.collect().await.map(|c| c.to_bytes()).map_err(|e| { - E::from(ServerFnErrorErr::Deserialization(e.to_string())) + ServerFnErrorErr::Deserialization(e.to_string()).into() }) } async fn try_into_string(self) -> Result { - // from: - let (_parts, body) = self.into_parts(); - - let bytes = - body.collect().await.map(|c| c.to_bytes()).map_err(|e| { - E::from(ServerFnErrorErr::Deserialization(e.to_string())) - })?; - // here: is the same as try_into_bytes, but rustc complains if `self.try_into_bytes()` called. - + let bytes = self.try_into_bytes().await?; String::from_utf8(bytes.to_vec()).map_err(|e| { - E::from(ServerFnErrorErr::Deserialization(e.to_string())) + ServerFnErrorErr::Deserialization(e.to_string()).into() }) } @@ -65,7 +57,7 @@ where ) -> Result> + Send + 'static, E> { Ok(self.into_body().into_data_stream().map(|chunk| { chunk.map_err(|e| { - E::from(ServerFnErrorErr::Deserialization(e.to_string())) + ServerFnErrorErr::Deserialization(e.to_string()).into() }) })) } diff --git a/server_fn/src/response/actix.rs b/server_fn/src/response/actix.rs index 443436f29e..8221877fd0 100644 --- a/server_fn/src/response/actix.rs +++ b/server_fn/src/response/actix.rs @@ -1,7 +1,6 @@ use super::Res; use crate::error::{ - FromServerFnError, ServerFnErrorSerde, ServerFnErrorWrapper, - SERVER_FN_ERROR_HEADER, + FromServerFnError, ServerFnErrorWrapper, SERVER_FN_ERROR_HEADER, }; use actix_web::{ http::{ diff --git a/server_fn/src/response/generic.rs b/server_fn/src/response/generic.rs index a0e385607a..644801481b 100644 --- a/server_fn/src/response/generic.rs +++ b/server_fn/src/response/generic.rs @@ -14,7 +14,7 @@ use super::Res; use crate::error::{ - FromServerFnError, ServerFnErrorErr, ServerFnErrorSerde, ServerFnErrorWrapper, + FromServerFnError, ServerFnErrorErr, ServerFnErrorWrapper, SERVER_FN_ERROR_HEADER, }; use bytes::Bytes; diff --git a/server_fn/src/response/http.rs b/server_fn/src/response/http.rs index e2cf580e4b..254bf4e36b 100644 --- a/server_fn/src/response/http.rs +++ b/server_fn/src/response/http.rs @@ -1,7 +1,7 @@ use super::Res; use crate::error::{ - FromServerFnError, ServerFnErrorErr, ServerFnErrorSerde, - ServerFnErrorWrapper, SERVER_FN_ERROR_HEADER, + FromServerFnError, ServerFnErrorErr, ServerFnErrorWrapper, + SERVER_FN_ERROR_HEADER, }; use axum::body::Body; use bytes::Bytes; From 861d70280aed348e58fdb7c8ea14143a8132333e Mon Sep 17 00:00:00 2001 From: Ryo Hirayama Date: Fri, 22 Nov 2024 13:33:19 +0900 Subject: [PATCH 07/15] Redo fd68c76 example fix --- examples/server_fns_axum/src/app.rs | 52 +++++++++++++++++------------ 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/examples/server_fns_axum/src/app.rs b/examples/server_fns_axum/src/app.rs index 75f5c8fe79..e64917fd60 100644 --- a/examples/server_fns_axum/src/app.rs +++ b/examples/server_fns_axum/src/app.rs @@ -9,6 +9,7 @@ use server_fn::{ MultipartFormData, Postcard, Rkyv, SerdeLite, StreamingText, TextStream, }, + error::{FromServerFnError, ServerFnErrorErr}, request::{browser::BrowserRequest, ClientReq, Req}, response::{browser::BrowserResponse, ClientRes, Res}, }; @@ -18,7 +19,7 @@ use std::sync::{ atomic::{AtomicU8, Ordering}, Mutex, }; -use strum::{Display, EnumString}; +use strum::Display; use wasm_bindgen::JsCast; use web_sys::{FormData, HtmlFormElement, SubmitEvent}; @@ -652,9 +653,7 @@ 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> { +pub async fn ascii_uppercase(text: String) -> Result { if text.len() < 5 { Err(InvalidArgument::TooShort.into()) } else if text.len() > 15 { @@ -667,11 +666,18 @@ pub async fn ascii_uppercase( } // The EnumString and Display derive macros are provided by strum -#[derive(Debug, Clone, EnumString, Display)] +#[derive(Debug, Clone, Display, Serialize, Deserialize)] pub enum InvalidArgument { TooShort, TooLong, NotAscii, + ServerFnError(ServerFnErrorErr), +} + +impl From for InvalidArgument { + fn from(value: ServerFnErrorErr) -> Self { + InvalidArgument::ServerFnError(value) + } } #[component] @@ -726,14 +732,11 @@ impl IntoReq for TomlEncoded where Request: ClientReq, T: Serialize, + Err: FromServerFnError, { - fn into_req( - self, - path: &str, - accepts: &str, - ) -> Result> { + fn into_req(self, path: &str, accepts: &str) -> Result { let data = toml::to_string(&self.0) - .map_err(|e| ServerFnError::Serialization(e.to_string()))?; + .map_err(|e| ServerFnErrorErr::Serialization(e.to_string()))?; Request::try_new_post(path, Toml::CONTENT_TYPE, accepts, data) } } @@ -742,12 +745,13 @@ impl FromReq for TomlEncoded where Request: Req + Send, T: DeserializeOwned, + Err: FromServerFnError, { - async fn from_req(req: Request) -> Result> { + async fn from_req(req: Request) -> Result { let string_data = req.try_into_string().await?; toml::from_str::(&string_data) .map(TomlEncoded) - .map_err(|e| ServerFnError::Args(e.to_string())) + .map_err(|e| ServerFnErrorErr::Args(e.to_string()).into()) } } @@ -755,10 +759,11 @@ impl IntoRes for TomlEncoded where Response: Res, T: Serialize + Send, + Err: FromServerFnError, { - async fn into_res(self) -> Result> { + async fn into_res(self) -> Result { let data = toml::to_string(&self.0) - .map_err(|e| ServerFnError::Serialization(e.to_string()))?; + .map_err(|e| ServerFnErrorErr::Serialization(e.to_string()))?; Response::try_from_string(Toml::CONTENT_TYPE, data) } } @@ -767,12 +772,13 @@ impl FromRes for TomlEncoded where Response: ClientRes + Send, T: DeserializeOwned, + Err: FromServerFnError, { - async fn from_res(res: Response) -> Result> { + async fn from_res(res: Response) -> Result { 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() + }) } } @@ -835,7 +841,10 @@ pub fn CustomClientExample() -> impl IntoView { pub struct CustomClient; // Implement the `Client` trait for it. - impl Client for CustomClient { + impl Client 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; @@ -844,8 +853,7 @@ pub fn CustomClientExample() -> impl IntoView { // Our custom `send()` implementation does all the work. fn send( req: Self::Request, - ) -> impl Future>> - + Send { + ) -> impl Future> + Send { // BrowserRequest derefs to the underlying Request type from gloo-net, // so we can get access to the headers here let headers = req.headers(); From 9f41274a7b2eba6fa2e830dcc25783579f36b48f Mon Sep 17 00:00:00 2001 From: Ryo Hirayama Date: Fri, 22 Nov 2024 13:43:05 +0900 Subject: [PATCH 08/15] Remove handwritten Serde impl --- server_fn/src/codec/stream.rs | 6 ++- server_fn/src/error.rs | 86 +++-------------------------------- 2 files changed, 10 insertions(+), 82 deletions(-) diff --git a/server_fn/src/codec/stream.rs b/server_fn/src/codec/stream.rs index 95958f968a..4b77e31c79 100644 --- a/server_fn/src/codec/stream.rs +++ b/server_fn/src/codec/stream.rs @@ -3,7 +3,7 @@ use crate::{ error::{FromServerFnError, ServerFnErrorErr}, request::{ClientReq, Req}, response::{ClientRes, Res}, - IntoRes, + IntoRes, ServerFnError, }; use bytes::Bytes; use futures::{Stream, StreamExt}; @@ -151,7 +151,9 @@ impl Encoding for StreamingText { /// end before the output will begin. /// /// Streaming requests are only allowed over HTTP2 or HTTP3. -pub struct TextStream(Pin> + Send>>); +pub struct TextStream( + Pin> + Send>>, +); impl Debug for TextStream { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/server_fn/src/error.rs b/server_fn/src/error.rs index 6f6fa8e24e..0b14aa02c7 100644 --- a/server_fn/src/error.rs +++ b/server_fn/src/error.rs @@ -2,11 +2,7 @@ use base64::{engine::general_purpose::URL_SAFE, Engine as _}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::{ - fmt, - fmt::{Display, Write}, - str::FromStr, -}; +use std::{fmt, fmt::Display, str::FromStr}; use thiserror::Error; use throw_error::Error; use url::Url; @@ -263,80 +259,6 @@ pub trait ServerFnErrorSerde: Sized { fn de(data: &str) -> Self; } -impl ServerFnErrorSerde for ServerFnErrorErr -where - CustErr: FromStr + Display, -{ - type Error = std::fmt::Error; - - fn ser(&self) -> Result { - let mut buf = String::new(); - match self { - ServerFnErrorErr::WrappedServerError(e) => { - write!(&mut buf, "WrappedServerFn|{e}") - } - ServerFnErrorErr::Registration(e) => { - write!(&mut buf, "Registration|{e}") - } - ServerFnErrorErr::Request(e) => write!(&mut buf, "Request|{e}"), - ServerFnErrorErr::Response(e) => write!(&mut buf, "Response|{e}"), - ServerFnErrorErr::ServerError(e) => { - write!(&mut buf, "ServerError|{e}") - } - ServerFnErrorErr::MiddlewareError(e) => { - write!(&mut buf, "MiddlewareError|{e}") - } - ServerFnErrorErr::Deserialization(e) => { - write!(&mut buf, "Deserialization|{e}") - } - ServerFnErrorErr::Serialization(e) => { - write!(&mut buf, "Serialization|{e}") - } - ServerFnErrorErr::Args(e) => write!(&mut buf, "Args|{e}"), - ServerFnErrorErr::MissingArg(e) => { - write!(&mut buf, "MissingArg|{e}") - } - }?; - Ok(buf) - } - - fn de(data: &str) -> Self { - data.split_once('|') - .and_then(|(ty, data)| match ty { - "WrappedServerFn" => match CustErr::from_str(data) { - Ok(d) => Some(ServerFnErrorErr::WrappedServerError(d)), - Err(_) => None, - }, - "Registration" => { - Some(ServerFnErrorErr::Registration(data.to_string())) - } - "Request" => Some(ServerFnErrorErr::Request(data.to_string())), - "Response" => { - Some(ServerFnErrorErr::Response(data.to_string())) - } - "ServerError" => { - Some(ServerFnErrorErr::ServerError(data.to_string())) - } - "Deserialization" => { - Some(ServerFnErrorErr::Deserialization(data.to_string())) - } - "Serialization" => { - Some(ServerFnErrorErr::Serialization(data.to_string())) - } - "Args" => Some(ServerFnErrorErr::Args(data.to_string())), - "MissingArg" => { - Some(ServerFnErrorErr::MissingArg(data.to_string())) - } - _ => None, - }) - .unwrap_or_else(|| { - ServerFnErrorErr::Deserialization(format!( - "Could not deserialize error {data:?}" - )) - }) - } -} - impl std::error::Error for ServerFnError where E: std::error::Error + 'static, @@ -359,7 +281,11 @@ where /// /// [`ServerFnError`] and [`ServerFnErrorErr`] mutually implement [`From`], so /// it is easy to convert between the two types. -#[derive(Error, Debug, Clone, PartialEq, Eq)] +#[derive(Error, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) +)] pub enum ServerFnErrorErr { /// A user-defined custom error type, which defaults to [`NoCustomError`]. #[error("internal error: {0}")] From 36cb1c145f74156d21b16d9ed67c29fe451a78fa Mon Sep 17 00:00:00 2001 From: Ryo Hirayama Date: Fri, 22 Nov 2024 17:03:32 +0900 Subject: [PATCH 09/15] Stop to use From trait --- examples/server_fns_axum/src/app.rs | 74 +++++++++++++++++++------ leptos/src/form.rs | 11 ++-- server_fn/src/client.rs | 13 ++--- server_fn/src/codec/cbor.rs | 10 ++-- server_fn/src/codec/json.rs | 19 ++++--- server_fn/src/codec/msgpack.rs | 10 ++-- server_fn/src/codec/postcard.rs | 10 ++-- server_fn/src/codec/rkyv.rs | 12 ++--- server_fn/src/codec/serde_lite.rs | 34 ++++++------ server_fn/src/codec/stream.rs | 8 +-- server_fn/src/codec/url.rs | 14 +++-- server_fn/src/error.rs | 83 +++++++++++++++-------------- server_fn/src/middleware/mod.rs | 9 ++-- server_fn/src/request/actix.rs | 13 +++-- server_fn/src/request/axum.rs | 9 ++-- server_fn/src/request/browser.rs | 28 +++++++--- server_fn/src/request/generic.rs | 4 +- server_fn/src/request/reqwest.rs | 29 +++++++--- server_fn/src/response/browser.rs | 11 ++-- server_fn/src/response/generic.rs | 14 +++-- server_fn/src/response/http.rs | 14 +++-- server_fn/src/response/reqwest.rs | 12 ++--- 22 files changed, 274 insertions(+), 167 deletions(-) diff --git a/examples/server_fns_axum/src/app.rs b/examples/server_fns_axum/src/app.rs index e64917fd60..2cfce8234d 100644 --- a/examples/server_fns_axum/src/app.rs +++ b/examples/server_fns_axum/src/app.rs @@ -9,7 +9,7 @@ use server_fn::{ MultipartFormData, Postcard, Rkyv, SerdeLite, StreamingText, TextStream, }, - error::{FromServerFnError, ServerFnErrorErr}, + error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::{browser::BrowserRequest, ClientReq, Req}, response::{browser::BrowserResponse, ClientRes, Res}, }; @@ -19,7 +19,7 @@ use std::sync::{ atomic::{AtomicU8, Ordering}, Mutex, }; -use strum::Display; +use strum::{Display, EnumString}; use wasm_bindgen::JsCast; use web_sys::{FormData, HtmlFormElement, SubmitEvent}; @@ -653,30 +653,64 @@ 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 { +pub async fn ascii_uppercase(text: String) -> Result { + other_error()?; + Ok(ascii_uppercase_inner(text)?) +} + +pub fn other_error() -> Result<(), String> { + Ok(()) +} + +pub fn ascii_uppercase_inner(text: String) -> Result { 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> { + Ok(ascii_uppercase_inner(text)?) +} + // The EnumString and Display derive macros are provided by strum -#[derive(Debug, Clone, Display, Serialize, Deserialize)] +#[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 for MyErrors { + fn from(value: InvalidArgument) -> Self { + MyErrors::InvalidArgument(value) + } +} + +impl From for MyErrors { + fn from(value: String) -> Self { + MyErrors::Other(value) + } } -impl From for InvalidArgument { - fn from(value: ServerFnErrorErr) -> Self { - InvalidArgument::ServerFnError(value) +impl FromServerFnError for MyErrors { + fn from_server_fn_error(value: ServerFnErrorErr) -> Self { + MyErrors::ServerFnError(value) } } @@ -684,6 +718,7 @@ impl From for InvalidArgument { pub fn CustomErrorTypes() -> impl IntoView { let input_ref = NodeRef::::new(); let (result, set_result) = signal(None); + let (result_classic, set_result_classic) = signal(None); view! {

Using custom error types

@@ -698,14 +733,17 @@ pub fn CustomErrorTypes() -> impl IntoView {

{move || format!("{:?}", result.get())}

+

{move || format!("{:?}", result_classic.get())}

} } @@ -735,8 +773,9 @@ where Err: FromServerFnError, { fn into_req(self, path: &str, accepts: &str) -> Result { - let data = toml::to_string(&self.0) - .map_err(|e| ServerFnErrorErr::Serialization(e.to_string()))?; + 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) } } @@ -751,7 +790,7 @@ where let string_data = req.try_into_string().await?; toml::from_str::(&string_data) .map(TomlEncoded) - .map_err(|e| ServerFnErrorErr::Args(e.to_string()).into()) + .map_err(|e| ServerFnErrorErr::Args(e.to_string()).into_app_error()) } } @@ -762,8 +801,9 @@ where Err: FromServerFnError, { async fn into_res(self) -> Result { - let data = toml::to_string(&self.0) - .map_err(|e| ServerFnErrorErr::Serialization(e.to_string()))?; + 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) } } @@ -777,7 +817,7 @@ where async fn from_res(res: Response) -> Result { let data = res.try_into_string().await?; toml::from_str(&data).map(TomlEncoded).map_err(|e| { - ServerFnErrorErr::Deserialization(e.to_string()).into() + ServerFnErrorErr::Deserialization(e.to_string()).into_app_error() }) } } diff --git a/leptos/src/form.rs b/leptos/src/form.rs index fb7f6b1989..a3da76f0cd 100644 --- a/leptos/src/form.rs +++ b/leptos/src/form.rs @@ -3,8 +3,11 @@ use leptos_dom::helpers::window; use leptos_server::{ServerAction, ServerMultiAction}; use serde::de::DeserializeOwned; use server_fn::{ - client::Client, codec::PostUrl, error::ServerFnErrorErr, - request::ClientReq, ServerFn, + client::Client, + codec::PostUrl, + error::{IntoAppError, ServerFnErrorErr}, + request::ClientReq, + ServerFn, }; use tachys::{ either::Either, @@ -127,7 +130,7 @@ where value.set(Some(Err(ServerFnErrorErr::Serialization( err.to_string(), ) - .into()))); + .into_app_error()))); version.update(|n| *n += 1); } } @@ -196,7 +199,7 @@ where action.dispatch_sync(Err(ServerFnErrorErr::Serialization( err.to_string(), ) - .into())); + .into_app_error())); } } }; diff --git a/server_fn/src/client.rs b/server_fn/src/client.rs index 47026d29a8..c67a60207c 100644 --- a/server_fn/src/client.rs +++ b/server_fn/src/client.rs @@ -38,7 +38,7 @@ pub trait Client { pub mod browser { use super::Client; use crate::{ - error::{FromServerFnError, ServerFnErrorErr}, + error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::browser::{BrowserRequest, RequestInner}, response::browser::BrowserResponse, }; @@ -66,7 +66,8 @@ pub mod browser { .await .map(|res| BrowserResponse(SendWrapper::new(res))) .map_err(|e| { - ServerFnErrorErr::Request(e.to_string()).into() + ServerFnErrorErr::Request(e.to_string()) + .into_app_error() }); // at this point, the future has successfully resolved without being dropped, so we @@ -85,7 +86,7 @@ pub mod browser { pub mod reqwest { use super::Client; use crate::{ - error::{FromServerFnError, ServerFnErrorErr}, + error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::reqwest::CLIENT, }; use futures::TryFutureExt; @@ -102,9 +103,9 @@ pub mod reqwest { fn send( req: Self::Request, ) -> impl Future> + Send { - CLIENT - .execute(req) - .map_err(|e| ServerFnErrorErr::Request(e.to_string()).into()) + CLIENT.execute(req).map_err(|e| { + ServerFnErrorErr::Request(e.to_string()).into_app_error() + }) } } } diff --git a/server_fn/src/codec/cbor.rs b/server_fn/src/codec/cbor.rs index 25d3d2a193..851b68b939 100644 --- a/server_fn/src/codec/cbor.rs +++ b/server_fn/src/codec/cbor.rs @@ -1,6 +1,6 @@ use super::{Encoding, FromReq, FromRes, IntoReq, IntoRes}; use crate::{ - error::{FromServerFnError, ServerFnErrorErr}, + error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::{ClientReq, Req}, response::{ClientRes, Res}, }; @@ -25,7 +25,7 @@ where fn into_req(self, path: &str, accepts: &str) -> Result { let mut buffer: Vec = Vec::new(); ciborium::ser::into_writer(&self, &mut buffer).map_err(|e| { - E::from(ServerFnErrorErr::Serialization(e.to_string())) + ServerFnErrorErr::Serialization(e.to_string()).into_app_error() })?; Request::try_new_post_bytes( path, @@ -45,7 +45,7 @@ where async fn from_req(req: Request) -> Result { let body_bytes = req.try_into_bytes().await?; ciborium::de::from_reader(body_bytes.as_ref()) - .map_err(|e| E::from(ServerFnErrorErr::Args(e.to_string()))) + .map_err(|e| ServerFnErrorErr::Args(e.to_string()).into_app_error()) } } @@ -58,7 +58,7 @@ where async fn into_res(self) -> Result { let mut buffer: Vec = Vec::new(); ciborium::ser::into_writer(&self, &mut buffer).map_err(|e| { - E::from(ServerFnErrorErr::Serialization(e.to_string())) + ServerFnErrorErr::Serialization(e.to_string()).into_app_error() })?; Response::try_from_bytes(Cbor::CONTENT_TYPE, Bytes::from(buffer)) } @@ -73,7 +73,7 @@ where async fn from_res(res: Response) -> Result { let data = res.try_into_bytes().await?; ciborium::de::from_reader(data.as_ref()) - .map_err(|e| E::from(ServerFnErrorErr::Args(e.to_string()))) + .map_err(|e| ServerFnErrorErr::Args(e.to_string()).into_app_error()) } } diff --git a/server_fn/src/codec/json.rs b/server_fn/src/codec/json.rs index 2df9c8c185..a0cf2fed95 100644 --- a/server_fn/src/codec/json.rs +++ b/server_fn/src/codec/json.rs @@ -1,6 +1,6 @@ use super::{Encoding, FromReq, FromRes, Streaming}; use crate::{ - error::{FromServerFnError, ServerFnErrorErr}, + error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::{ClientReq, Req}, response::{ClientRes, Res}, IntoReq, IntoRes, @@ -26,7 +26,7 @@ where { fn into_req(self, path: &str, accepts: &str) -> Result { let data = serde_json::to_string(&self).map_err(|e| { - E::from(ServerFnErrorErr::Serialization(e.to_string())) + ServerFnErrorErr::Serialization(e.to_string()).into_app_error() })?; Request::try_new_post(path, accepts, Json::CONTENT_TYPE, data) } @@ -41,7 +41,7 @@ where async fn from_req(req: Request) -> Result { let string_data = req.try_into_string().await?; serde_json::from_str::(&string_data) - .map_err(|e| E::from(ServerFnErrorErr::Args(e.to_string()))) + .map_err(|e| ServerFnErrorErr::Args(e.to_string()).into_app_error()) } } @@ -53,7 +53,7 @@ where { async fn into_res(self) -> Result { let data = serde_json::to_string(&self).map_err(|e| { - E::from(ServerFnErrorErr::Serialization(e.to_string())) + ServerFnErrorErr::Serialization(e.to_string()).into_app_error() })?; Response::try_from_string(Json::CONTENT_TYPE, data) } @@ -68,7 +68,7 @@ where async fn from_res(res: Response) -> Result { let data = res.try_into_string().await?; serde_json::from_str(&data).map_err(|e| { - E::from(ServerFnErrorErr::Deserialization(e.to_string())) + ServerFnErrorErr::Deserialization(e.to_string()).into_app_error() }) } } @@ -173,7 +173,8 @@ where let s = JsonStream::new(data.map(|chunk| { chunk.and_then(|bytes| { serde_json::from_slice(bytes.as_ref()).map_err(|e| { - E::from(ServerFnErrorErr::Deserialization(e.to_string())) + ServerFnErrorErr::Deserialization(e.to_string()) + .into_app_error() }) }) })); @@ -192,7 +193,8 @@ where Streaming::CONTENT_TYPE, self.into_inner().map(|value| { serde_json::to_vec(&value?).map(Bytes::from).map_err(|e| { - ServerFnErrorErr::Serialization(e.to_string()).into() + ServerFnErrorErr::Serialization(e.to_string()) + .into_app_error() }) }), ) @@ -210,7 +212,8 @@ where Ok(JsonStream::new(stream.map(|chunk| { chunk.and_then(|bytes| { serde_json::from_slice(bytes.as_ref()).map_err(|e| { - ServerFnErrorErr::Deserialization(e.to_string()).into() + ServerFnErrorErr::Deserialization(e.to_string()) + .into_app_error() }) }) }))) diff --git a/server_fn/src/codec/msgpack.rs b/server_fn/src/codec/msgpack.rs index 5ba9c0d253..3d13c9235d 100644 --- a/server_fn/src/codec/msgpack.rs +++ b/server_fn/src/codec/msgpack.rs @@ -1,6 +1,6 @@ use super::{Encoding, FromReq, FromRes, IntoReq, IntoRes}; use crate::{ - error::{FromServerFnError, ServerFnErrorErr}, + error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::{ClientReq, Req}, response::{ClientRes, Res}, }; @@ -24,7 +24,7 @@ where { fn into_req(self, path: &str, accepts: &str) -> Result { let data = rmp_serde::to_vec(&self).map_err(|e| { - E::from(ServerFnErrorErr::Serialization(e.to_string())) + ServerFnErrorErr::Serialization(e.to_string()).into_app_error() })?; Request::try_new_post_bytes( path, @@ -44,7 +44,7 @@ where async fn from_req(req: Request) -> Result { let data = req.try_into_bytes().await?; rmp_serde::from_slice::(&data) - .map_err(|e| ServerFnErrorErr::Args(e.to_string()).into()) + .map_err(|e| ServerFnErrorErr::Args(e.to_string()).into_app_error()) } } @@ -56,7 +56,7 @@ where { async fn into_res(self) -> Result { let data = rmp_serde::to_vec(&self).map_err(|e| { - E::from(ServerFnErrorErr::Serialization(e.to_string())) + ServerFnErrorErr::Serialization(e.to_string()).into_app_error() })?; Response::try_from_bytes(MsgPack::CONTENT_TYPE, Bytes::from(data)) } @@ -71,7 +71,7 @@ where async fn from_res(res: Response) -> Result { let data = res.try_into_bytes().await?; rmp_serde::from_slice(&data).map_err(|e| { - E::from(ServerFnErrorErr::Deserialization(e.to_string())) + ServerFnErrorErr::Deserialization(e.to_string()).into_app_error() }) } } diff --git a/server_fn/src/codec/postcard.rs b/server_fn/src/codec/postcard.rs index 65f80cfcb0..6e07c8b50b 100644 --- a/server_fn/src/codec/postcard.rs +++ b/server_fn/src/codec/postcard.rs @@ -1,6 +1,6 @@ use super::{Encoding, FromReq, FromRes, IntoReq, IntoRes}; use crate::{ - error::{FromServerFnError, ServerFnErrorErr}, + error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::{ClientReq, Req}, response::{ClientRes, Res}, }; @@ -24,7 +24,7 @@ where { fn into_req(self, path: &str, accepts: &str) -> Result { let data = postcard::to_allocvec(&self).map_err(|e| { - E::from(ServerFnErrorErr::Serialization(e.to_string())) + ServerFnErrorErr::Serialization(e.to_string()).into_app_error() })?; Request::try_new_post_bytes( path, @@ -44,7 +44,7 @@ where async fn from_req(req: Request) -> Result { let data = req.try_into_bytes().await?; postcard::from_bytes::(&data) - .map_err(|e| E::from(ServerFnErrorErr::Args(e.to_string()))) + .map_err(|e| ServerFnErrorErr::Args(e.to_string()).into_app_error()) } } @@ -56,7 +56,7 @@ where { async fn into_res(self) -> Result { let data = postcard::to_allocvec(&self).map_err(|e| { - E::from(ServerFnErrorErr::Serialization(e.to_string())) + ServerFnErrorErr::Serialization(e.to_string()).into_app_error() })?; Response::try_from_bytes(Postcard::CONTENT_TYPE, Bytes::from(data)) } @@ -71,7 +71,7 @@ where async fn from_res(res: Response) -> Result { let data = res.try_into_bytes().await?; postcard::from_bytes(&data).map_err(|e| { - E::from(ServerFnErrorErr::Deserialization(e.to_string())) + ServerFnErrorErr::Deserialization(e.to_string()).into_app_error() }) } } diff --git a/server_fn/src/codec/rkyv.rs b/server_fn/src/codec/rkyv.rs index 86643b3709..d45087d9b9 100644 --- a/server_fn/src/codec/rkyv.rs +++ b/server_fn/src/codec/rkyv.rs @@ -1,6 +1,6 @@ use super::{Encoding, FromReq, FromRes, IntoReq, IntoRes}; use crate::{ - error::{FromServerFnError, ServerFnErrorErr}, + error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::{ClientReq, Req}, response::{ClientRes, Res}, }; @@ -39,7 +39,7 @@ where { fn into_req(self, path: &str, accepts: &str) -> Result { let encoded = rkyv::to_bytes::(&self).map_err(|e| { - E::from(ServerFnErrorErr::Serialization(e.to_string())) + ServerFnErrorErr::Serialization(e.to_string()).into_app_error() })?; let bytes = Bytes::copy_from_slice(encoded.as_ref()); Request::try_new_post_bytes(path, accepts, Rkyv::CONTENT_TYPE, bytes) @@ -63,7 +63,7 @@ where return Err(ServerFnErrorErr::Deserialization( e.to_string(), ) - .into()) + .into_app_error()); } Ok(bytes) => { for byte in bytes { @@ -73,7 +73,7 @@ where } } rkyv::from_bytes::(aligned.as_ref()) - .map_err(|e| ServerFnErrorErr::Args(e.to_string()).into()) + .map_err(|e| ServerFnErrorErr::Args(e.to_string()).into_app_error()) } } @@ -88,7 +88,7 @@ where { async fn into_res(self) -> Result { let encoded = rkyv::to_bytes::(&self).map_err(|e| { - E::from(ServerFnErrorErr::Serialization(format!("{e:?}"))) + ServerFnErrorErr::Serialization(format!("{e:?}")).into_app_error() })?; let bytes = Bytes::copy_from_slice(encoded.as_ref()); Response::try_from_bytes(Rkyv::CONTENT_TYPE, bytes) @@ -106,7 +106,7 @@ where async fn from_res(res: Response) -> Result { let data = res.try_into_bytes().await?; rkyv::from_bytes::(&data).map_err(|e| { - ServerFnErrorErr::Deserialization(e.to_string()).into() + ServerFnErrorErr::Deserialization(e.to_string()).into_app_error() }) } } diff --git a/server_fn/src/codec/serde_lite.rs b/server_fn/src/codec/serde_lite.rs index 319d698a81..b0c2cc4be4 100644 --- a/server_fn/src/codec/serde_lite.rs +++ b/server_fn/src/codec/serde_lite.rs @@ -1,6 +1,6 @@ use super::{Encoding, FromReq, FromRes}; use crate::{ - error::{FromServerFnError, ServerFnErrorErr}, + error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::{ClientReq, Req}, response::{ClientRes, Res}, IntoReq, IntoRes, @@ -23,9 +23,11 @@ where { fn into_req(self, path: &str, accepts: &str) -> Result { let data = serde_json::to_string(&self.serialize().map_err(|e| { - E::from(ServerFnErrorErr::Serialization(e.to_string())) + ServerFnErrorErr::Serialization(e.to_string()).into_app_error() })?) - .map_err(|e| E::from(ServerFnErrorErr::Serialization(e.to_string())))?; + .map_err(|e| { + ServerFnErrorErr::Serialization(e.to_string()).into_app_error() + })?; Request::try_new_post(path, accepts, SerdeLite::CONTENT_TYPE, data) } } @@ -38,11 +40,10 @@ where { async fn from_req(req: Request) -> Result { let string_data = req.try_into_string().await?; - Self::deserialize( - &serde_json::from_str(&string_data) - .map_err(|e| E::from(ServerFnErrorErr::Args(e.to_string())))?, - ) - .map_err(|e| E::from(ServerFnErrorErr::Args(e.to_string()))) + Self::deserialize(&serde_json::from_str(&string_data).map_err(|e| { + ServerFnErrorErr::Args(e.to_string()).into_app_error() + })?) + .map_err(|e| ServerFnErrorErr::Args(e.to_string()).into_app_error()) } } @@ -54,9 +55,11 @@ where { async fn into_res(self) -> Result { let data = serde_json::to_string(&self.serialize().map_err(|e| { - E::from(ServerFnErrorErr::Serialization(e.to_string())) + ServerFnErrorErr::Serialization(e.to_string()).into_app_error() })?) - .map_err(|e| E::from(ServerFnErrorErr::Serialization(e.to_string())))?; + .map_err(|e| { + ServerFnErrorErr::Serialization(e.to_string()).into_app_error() + })?; Response::try_from_string(SerdeLite::CONTENT_TYPE, data) } } @@ -69,10 +72,11 @@ where { async fn from_res(res: Response) -> Result { let data = res.try_into_string().await?; - Self::deserialize( - &serde_json::from_str(&data) - .map_err(|e| E::from(ServerFnErrorErr::Args(e.to_string())))?, - ) - .map_err(|e| E::from(ServerFnErrorErr::Deserialization(e.to_string()))) + Self::deserialize(&serde_json::from_str(&data).map_err(|e| { + ServerFnErrorErr::Args(e.to_string()).into_app_error() + })?) + .map_err(|e| { + ServerFnErrorErr::Deserialization(e.to_string()).into_app_error() + }) } } diff --git a/server_fn/src/codec/stream.rs b/server_fn/src/codec/stream.rs index 4b77e31c79..977a22c8bf 100644 --- a/server_fn/src/codec/stream.rs +++ b/server_fn/src/codec/stream.rs @@ -1,6 +1,6 @@ use super::{Encoding, FromReq, FromRes, IntoReq}; use crate::{ - error::{FromServerFnError, ServerFnErrorErr}, + error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::{ClientReq, Req}, response::{ClientRes, Res}, IntoRes, ServerFnError, @@ -215,7 +215,8 @@ where let s = TextStream::new(data.map(|chunk| { chunk.and_then(|bytes| { String::from_utf8(bytes.to_vec()).map_err(|e| { - ServerFnErrorErr::Deserialization(e.to_string()).into() + ServerFnErrorErr::Deserialization(e.to_string()) + .into_app_error() }) }) })); @@ -246,7 +247,8 @@ where Ok(TextStream(Box::pin(stream.map(|chunk| { chunk.and_then(|bytes| { String::from_utf8(bytes.into()).map_err(|e| { - ServerFnErrorErr::Deserialization(e.to_string()).into() + ServerFnErrorErr::Deserialization(e.to_string()) + .into_app_error() }) }) })))) diff --git a/server_fn/src/codec/url.rs b/server_fn/src/codec/url.rs index 79eca2d7d5..d5a3e7975c 100644 --- a/server_fn/src/codec/url.rs +++ b/server_fn/src/codec/url.rs @@ -1,6 +1,6 @@ use super::{Encoding, FromReq, IntoReq}; use crate::{ - error::{FromServerFnError, ServerFnErrorErr}, + error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::{ClientReq, Req}, }; use http::Method; @@ -25,7 +25,7 @@ where { fn into_req(self, path: &str, accepts: &str) -> Result { let data = serde_qs::to_string(&self).map_err(|e| { - E::from(ServerFnErrorErr::Serialization(e.to_string())) + ServerFnErrorErr::Serialization(e.to_string()).into_app_error() })?; Request::try_new_get(path, accepts, GetUrl::CONTENT_TYPE, &data) } @@ -41,7 +41,9 @@ where let string_data = req.as_query().unwrap_or_default(); let args = serde_qs::Config::new(5, false) .deserialize_str::(string_data) - .map_err(|e| E::from(ServerFnErrorErr::Args(e.to_string())))?; + .map_err(|e| { + ServerFnErrorErr::Args(e.to_string()).into_app_error() + })?; Ok(args) } } @@ -59,7 +61,7 @@ where { fn into_req(self, path: &str, accepts: &str) -> Result { let qs = serde_qs::to_string(&self).map_err(|e| { - E::from(ServerFnErrorErr::Serialization(e.to_string())) + ServerFnErrorErr::Serialization(e.to_string()).into_app_error() })?; Request::try_new_post(path, accepts, PostUrl::CONTENT_TYPE, qs) } @@ -75,7 +77,9 @@ where let string_data = req.try_into_string().await?; let args = serde_qs::Config::new(5, false) .deserialize_str::(&string_data) - .map_err(|e| E::from(ServerFnErrorErr::Args(e.to_string())))?; + .map_err(|e| { + ServerFnErrorErr::Args(e.to_string()).into_app_error() + })?; Ok(args) } } diff --git a/server_fn/src/error.rs b/server_fn/src/error.rs index 0b14aa02c7..bda864b94b 100644 --- a/server_fn/src/error.rs +++ b/server_fn/src/error.rs @@ -12,7 +12,7 @@ pub const SERVER_FN_ERROR_HEADER: &str = "serverfnerror"; impl From for Error { fn from(e: ServerFnError) -> Self { - Error::from(ServerFnErrorErr::from(e)) + Error::from(ServerFnErrorWrapper(e)) } } @@ -286,10 +286,7 @@ where feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) )] -pub enum ServerFnErrorErr { - /// A user-defined custom error type, which defaults to [`NoCustomError`]. - #[error("internal error: {0}")] - WrappedServerError(E), +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), @@ -319,33 +316,33 @@ pub enum ServerFnErrorErr { Response(String), } -impl From> for ServerFnErrorErr { - fn from(value: ServerFnError) -> Self { +impl FromServerFnError for ServerFnError +where + CustErr: std::fmt::Debug + Display + Serialize + DeserializeOwned + 'static, +{ + fn from_server_fn_error(value: ServerFnErrorErr) -> Self { match value { - ServerFnError::Registration(value) => { - ServerFnErrorErr::Registration(value) + ServerFnErrorErr::Registration(value) => { + ServerFnError::Registration(value) } - ServerFnError::Request(value) => ServerFnErrorErr::Request(value), - ServerFnError::ServerError(value) => { - ServerFnErrorErr::ServerError(value) + ServerFnErrorErr::Request(value) => ServerFnError::Request(value), + ServerFnErrorErr::ServerError(value) => { + ServerFnError::ServerError(value) } - ServerFnError::MiddlewareError(value) => { - ServerFnErrorErr::MiddlewareError(value) + ServerFnErrorErr::MiddlewareError(value) => { + ServerFnError::MiddlewareError(value) } - ServerFnError::Deserialization(value) => { - ServerFnErrorErr::Deserialization(value) + ServerFnErrorErr::Deserialization(value) => { + ServerFnError::Deserialization(value) } - ServerFnError::Serialization(value) => { - ServerFnErrorErr::Serialization(value) + ServerFnErrorErr::Serialization(value) => { + ServerFnError::Serialization(value) } - ServerFnError::Args(value) => ServerFnErrorErr::Args(value), - ServerFnError::MissingArg(value) => { - ServerFnErrorErr::MissingArg(value) + ServerFnErrorErr::Args(value) => ServerFnError::Args(value), + ServerFnErrorErr::MissingArg(value) => { + ServerFnError::MissingArg(value) } - ServerFnError::WrappedServerError(value) => { - ServerFnErrorErr::WrappedServerError(value) - } - ServerFnError::Response(value) => ServerFnErrorErr::Response(value), + ServerFnErrorErr::Response(value) => ServerFnError::Response(value), } } } @@ -423,14 +420,14 @@ impl ServerFnUrlError { Ok(decoded) => decoded, Err(err) => { return ServerFnErrorErr::Deserialization(err.to_string()) - .into() + .into_app_error(); } }; let s = match String::from_utf8(decoded) { Ok(s) => s, Err(err) => { return ServerFnErrorErr::Deserialization(err.to_string()) - .into() + .into_app_error(); } }; E::de(&s) @@ -468,12 +465,25 @@ impl std::error::Error for ServerFnErrorWrapper { /// A trait for types that can be returned from a server function. pub trait FromServerFnError: - Display - + std::fmt::Debug - + ServerFnErrorSerde - + From - + 'static + Display + std::fmt::Debug + ServerFnErrorSerde + 'static { + /// Converts a [`ServerFnErrorErr`] into the application-specific custom error type. + fn from_server_fn_error(value: ServerFnErrorErr) -> Self; +} + +/// A helper trait for converting a [`ServerFnErrorErr`] into an application-specific custom error type that implements [`FromServerFnError`]. +pub trait IntoAppError { + /// Converts a [`ServerFnErrorErr`] into the application-specific custom error type. + fn into_app_error(self) -> E; +} + +impl IntoAppError for ServerFnErrorErr +where + E: FromServerFnError, +{ + fn into_app_error(self) -> E { + E::from_server_fn_error(self) + } } #[test] @@ -483,15 +493,6 @@ fn assert_from_server_fn_error_impl() { assert_impl::(); } -impl FromServerFnError for E where - E: Display - + std::fmt::Debug - + ServerFnErrorSerde - + From - + 'static -{ -} - impl ServerFnErrorSerde for E where E: Serialize + DeserializeOwned, diff --git a/server_fn/src/middleware/mod.rs b/server_fn/src/middleware/mod.rs index dba6d4c204..deeeb46ce5 100644 --- a/server_fn/src/middleware/mod.rs +++ b/server_fn/src/middleware/mod.rs @@ -32,6 +32,7 @@ mod axum { use crate::{ error::{FromServerFnError, ServerFnErrorErr}, response::Res, + ServerFnError, }; use axum::body::Body; use http::{Request, Response}; @@ -51,7 +52,7 @@ mod axum { let inner = self.call(req); Box::pin(async move { inner.await.unwrap_or_else(|e| { - let err = S::Error::from( + let err = S::Error::from_server_fn_error( ServerFnErrorErr::MiddlewareError(e.to_string()), ); Response::::error_response(&path, &err) @@ -64,7 +65,7 @@ mod axum { for BoxedService, Response> { type Response = Response; - type Error = ServerFnErrorErr; + type Error = ServerFnError; type Future = Pin< Box< dyn std::future::Future< @@ -127,7 +128,7 @@ mod actix { let inner = self.call(req); Box::pin(async move { inner.await.unwrap_or_else(|e| { - let err = S::Error::from( + let err = S::Error::from_server_fn_error( ServerFnErrorErr::MiddlewareError(e.to_string()), ); ActixResponse::error_response(&path, &err).take() @@ -150,7 +151,7 @@ mod actix { let inner = self.call(req.0.take().0); Box::pin(async move { ActixResponse::from(inner.await.unwrap_or_else(|e| { - let err = S::Error::from( + let err = S::Error::from_server_fn_error( ServerFnErrorErr::MiddlewareError(e.to_string()), ); ActixResponse::error_response(&path, &err).take() diff --git a/server_fn/src/request/actix.rs b/server_fn/src/request/actix.rs index 98365f9575..1193f485bd 100644 --- a/server_fn/src/request/actix.rs +++ b/server_fn/src/request/actix.rs @@ -1,5 +1,5 @@ use crate::{ - error::{FromServerFnError, ServerFnErrorErr}, + error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::Req, }; use actix_web::{web::Payload, HttpRequest}; @@ -62,7 +62,8 @@ where SendWrapper::new(async move { let payload = self.0.take().1; payload.to_bytes().await.map_err(|e| { - ServerFnErrorErr::Deserialization(e.to_string()).into() + ServerFnErrorErr::Deserialization(e.to_string()) + .into_app_error() }) }) } @@ -73,10 +74,14 @@ where SendWrapper::new(async move { let payload = self.0.take().1; let bytes = payload.to_bytes().await.map_err(|e| { - E::from(ServerFnErrorErr::Deserialization(e.to_string())) + E::from_server_fn_error(ServerFnErrorErr::Deserialization( + e.to_string(), + )) })?; String::from_utf8(bytes.into()).map_err(|e| { - E::from(ServerFnErrorErr::Deserialization(e.to_string())) + E::from_server_fn_error(ServerFnErrorErr::Deserialization( + e.to_string(), + )) }) }) } diff --git a/server_fn/src/request/axum.rs b/server_fn/src/request/axum.rs index 1ae63c3f1b..da02a535eb 100644 --- a/server_fn/src/request/axum.rs +++ b/server_fn/src/request/axum.rs @@ -1,5 +1,5 @@ use crate::{ - error::{FromServerFnError, ServerFnErrorErr}, + error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::Req, }; use axum::body::{Body, Bytes}; @@ -41,14 +41,14 @@ where let (_parts, body) = self.into_parts(); body.collect().await.map(|c| c.to_bytes()).map_err(|e| { - ServerFnErrorErr::Deserialization(e.to_string()).into() + ServerFnErrorErr::Deserialization(e.to_string()).into_app_error() }) } async fn try_into_string(self) -> Result { let bytes = self.try_into_bytes().await?; String::from_utf8(bytes.to_vec()).map_err(|e| { - ServerFnErrorErr::Deserialization(e.to_string()).into() + ServerFnErrorErr::Deserialization(e.to_string()).into_app_error() }) } @@ -57,7 +57,8 @@ where ) -> Result> + Send + 'static, E> { Ok(self.into_body().into_data_stream().map(|chunk| { chunk.map_err(|e| { - ServerFnErrorErr::Deserialization(e.to_string()).into() + ServerFnErrorErr::Deserialization(e.to_string()) + .into_app_error() }) })) } diff --git a/server_fn/src/request/browser.rs b/server_fn/src/request/browser.rs index 79d732bd35..839c4d8fe7 100644 --- a/server_fn/src/request/browser.rs +++ b/server_fn/src/request/browser.rs @@ -114,7 +114,9 @@ where .abort_signal(abort_signal.as_ref()) .build() .map_err(|e| { - E::from(ServerFnErrorErr::Request(e.to_string())) + E::from_server_fn_error(ServerFnErrorErr::Request( + e.to_string(), + )) })?, abort_ctrl, }))) @@ -138,7 +140,9 @@ where .abort_signal(abort_signal.as_ref()) .body(body) .map_err(|e| { - E::from(ServerFnErrorErr::Request(e.to_string())) + E::from_server_fn_error(ServerFnErrorErr::Request( + e.to_string(), + )) })?, abort_ctrl, }))) @@ -164,7 +168,9 @@ where .abort_signal(abort_signal.as_ref()) .body(body) .map_err(|e| { - E::from(ServerFnErrorErr::Request(e.to_string())) + E::from_server_fn_error(ServerFnErrorErr::Request( + e.to_string(), + )) })?, abort_ctrl, }))) @@ -186,7 +192,9 @@ where .abort_signal(abort_signal.as_ref()) .body(body.0.take()) .map_err(|e| { - E::from(ServerFnErrorErr::Request(e.to_string())) + E::from_server_fn_error(ServerFnErrorErr::Request( + e.to_string(), + )) })?, abort_ctrl, }))) @@ -203,7 +211,7 @@ where let url_params = UrlSearchParams::new_with_str_sequence_sequence(&form_data) .map_err(|e| { - E::from(ServerFnErrorErr::Serialization( + E::from_server_fn_error(ServerFnErrorErr::Serialization( e.as_string().unwrap_or_else(|| { "Could not serialize FormData to URLSearchParams" .to_string() @@ -217,7 +225,9 @@ where .abort_signal(abort_signal.as_ref()) .body(url_params) .map_err(|e| { - E::from(ServerFnErrorErr::Request(e.to_string())) + E::from_server_fn_error(ServerFnErrorErr::Request( + e.to_string(), + )) })?, abort_ctrl, }))) @@ -232,7 +242,11 @@ where // TODO abort signal let (request, abort_ctrl) = streaming_request(path, accepts, content_type, body).map_err( - |e| E::from(ServerFnErrorErr::Request(format!("{e:?}"))), + |e| { + E::from_server_fn_error(ServerFnErrorErr::Request(format!( + "{e:?}" + ))) + }, )?; Ok(Self(SendWrapper::new(RequestInner { request, diff --git a/server_fn/src/request/generic.rs b/server_fn/src/request/generic.rs index e8453e3dc8..99a2838577 100644 --- a/server_fn/src/request/generic.rs +++ b/server_fn/src/request/generic.rs @@ -13,7 +13,7 @@ //! crate under the hood. use crate::{ - error::{FromServerFnError, ServerFnErrorErr}, + error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::Req, }; use bytes::Bytes; @@ -34,7 +34,7 @@ where async fn try_into_string(self) -> Result { String::from_utf8(self.into_body().into()).map_err(|err| { - ServerFnErrorErr::Deserialization(err.to_string()).into() + ServerFnErrorErr::Deserialization(err.to_string()).into_app_error() }) } diff --git a/server_fn/src/request/reqwest.rs b/server_fn/src/request/reqwest.rs index 6159c65ee7..e1ade8d76b 100644 --- a/server_fn/src/request/reqwest.rs +++ b/server_fn/src/request/reqwest.rs @@ -1,7 +1,7 @@ use super::ClientReq; use crate::{ client::get_server_url, - error::{FromServerFnError, ServerFnErrorErr}, + error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, }; use bytes::Bytes; use futures::Stream; @@ -24,15 +24,20 @@ where query: &str, ) -> Result { let url = format!("{}{}", get_server_url(), path); - let mut url = Url::try_from(url.as_str()) - .map_err(|e| E::from(ServerFnErrorErr::Request(e.to_string())))?; + let mut url = Url::try_from(url.as_str()).map_err(|e| { + E::from_server_fn_error(ServerFnErrorErr::Request(e.to_string())) + })?; url.set_query(Some(query)); let req = CLIENT .get(url) .header(CONTENT_TYPE, content_type) .header(ACCEPT, accepts) .build() - .map_err(|e| E::from(ServerFnErrorErr::Request(e.to_string())))?; + .map_err(|e| { + E::from_server_fn_error(ServerFnErrorErr::Request( + e.to_string(), + )) + })?; Ok(req) } @@ -49,7 +54,9 @@ where .header(ACCEPT, accepts) .body(body) .build() - .map_err(|e| ServerFnErrorErr::Request(e.to_string()).into()) + .map_err(|e| { + ServerFnErrorErr::Request(e.to_string()).into_app_error() + }) } fn try_new_post_bytes( @@ -65,7 +72,9 @@ where .header(ACCEPT, accepts) .body(body) .build() - .map_err(|e| ServerFnErrorErr::Request(e.to_string()).into()) + .map_err(|e| { + ServerFnErrorErr::Request(e.to_string()).into_app_error() + }) } fn try_new_multipart( @@ -78,7 +87,9 @@ where .header(ACCEPT, accepts) .multipart(body) .build() - .map_err(|e| ServerFnErrorErr::Request(e.to_string()).into()) + .map_err(|e| { + ServerFnErrorErr::Request(e.to_string()).into_app_error() + }) } fn try_new_post_form_data( @@ -93,7 +104,9 @@ where .header(ACCEPT, accepts) .multipart(body) .build() - .map_err(|e| ServerFnErrorErr::Request(e.to_string()).into()) + .map_err(|e| { + ServerFnErrorErr::Request(e.to_string()).into_app_error() + }) } fn try_new_streaming( diff --git a/server_fn/src/response/browser.rs b/server_fn/src/response/browser.rs index f03750aaf8..8f16f03de9 100644 --- a/server_fn/src/response/browser.rs +++ b/server_fn/src/response/browser.rs @@ -1,6 +1,6 @@ use super::ClientRes; use crate::{ - error::{FromServerFnError, ServerFnErrorErr}, + error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, redirect::REDIRECT_HEADER, }; use bytes::Bytes; @@ -21,7 +21,8 @@ impl ClientRes for BrowserResponse { // so we can safely wrap this SendWrapper::new(async move { self.0.text().await.map_err(|e| { - ServerFnErrorErr::Deserialization(e.to_string()).into() + ServerFnErrorErr::Deserialization(e.to_string()) + .into_app_error() }) }) } @@ -31,7 +32,8 @@ impl ClientRes for BrowserResponse { // so we can safely wrap this SendWrapper::new(async move { self.0.binary().await.map(Bytes::from).map_err(|e| { - ServerFnErrorErr::Deserialization(e.to_string()).into() + ServerFnErrorErr::Deserialization(e.to_string()) + .into_app_error() }) }) } @@ -44,7 +46,8 @@ impl ClientRes for BrowserResponse { .map(|data| match data { Err(e) => { web_sys::console::error_1(&e); - Err(ServerFnErrorErr::Request(format!("{e:?}")).into()) + Err(ServerFnErrorErr::Request(format!("{e:?}")) + .into_app_error()) } Ok(data) => { let data = data.unchecked_into::(); diff --git a/server_fn/src/response/generic.rs b/server_fn/src/response/generic.rs index 644801481b..f7909dcbf4 100644 --- a/server_fn/src/response/generic.rs +++ b/server_fn/src/response/generic.rs @@ -14,7 +14,7 @@ use super::Res; use crate::error::{ - FromServerFnError, ServerFnErrorErr, ServerFnErrorWrapper, + FromServerFnError, IntoAppError, ServerFnErrorErr, ServerFnErrorWrapper, SERVER_FN_ERROR_HEADER, }; use bytes::Bytes; @@ -51,7 +51,9 @@ where .status(200) .header(http::header::CONTENT_TYPE, content_type) .body(data.into()) - .map_err(|e| ServerFnErrorErr::Response(e.to_string()).into()) + .map_err(|e| { + ServerFnErrorErr::Response(e.to_string()).into_app_error() + }) } fn try_from_bytes(content_type: &str, data: Bytes) -> Result { @@ -60,7 +62,9 @@ where .status(200) .header(http::header::CONTENT_TYPE, content_type) .body(Body::Sync(data)) - .map_err(|e| ServerFnErrorErr::Response(e.to_string()).into()) + .map_err(|e| { + ServerFnErrorErr::Response(e.to_string()).into_app_error() + }) } fn try_from_stream( @@ -74,7 +78,9 @@ where .body(Body::Async(Box::pin( data.map_err(ServerFnErrorWrapper).map_err(Error::from), ))) - .map_err(|e| ServerFnErrorErr::Response(e.to_string()).into()) + .map_err(|e| { + ServerFnErrorErr::Response(e.to_string()).into_app_error() + }) } fn error_response(path: &str, err: &E) -> Self { diff --git a/server_fn/src/response/http.rs b/server_fn/src/response/http.rs index 254bf4e36b..3d097c8729 100644 --- a/server_fn/src/response/http.rs +++ b/server_fn/src/response/http.rs @@ -1,6 +1,6 @@ use super::Res; use crate::error::{ - FromServerFnError, ServerFnErrorErr, ServerFnErrorWrapper, + FromServerFnError, IntoAppError, ServerFnErrorErr, ServerFnErrorWrapper, SERVER_FN_ERROR_HEADER, }; use axum::body::Body; @@ -18,7 +18,9 @@ where .status(200) .header(http::header::CONTENT_TYPE, content_type) .body(Body::from(data)) - .map_err(|e| ServerFnErrorErr::Response(e.to_string()).into()) + .map_err(|e| { + ServerFnErrorErr::Response(e.to_string()).into_app_error() + }) } fn try_from_bytes(content_type: &str, data: Bytes) -> Result { @@ -27,7 +29,9 @@ where .status(200) .header(http::header::CONTENT_TYPE, content_type) .body(Body::from(data)) - .map_err(|e| ServerFnErrorErr::Response(e.to_string()).into()) + .map_err(|e| { + ServerFnErrorErr::Response(e.to_string()).into_app_error() + }) } fn try_from_stream( @@ -40,7 +44,9 @@ where .status(200) .header(http::header::CONTENT_TYPE, content_type) .body(body) - .map_err(|e| E::from(ServerFnErrorErr::Response(e.to_string()))) + .map_err(|e| { + ServerFnErrorErr::Response(e.to_string()).into_app_error() + }) } fn error_response(path: &str, err: &E) -> Self { diff --git a/server_fn/src/response/reqwest.rs b/server_fn/src/response/reqwest.rs index 79086ab0bf..7bbff5e8cc 100644 --- a/server_fn/src/response/reqwest.rs +++ b/server_fn/src/response/reqwest.rs @@ -1,5 +1,5 @@ use super::ClientRes; -use crate::error::{FromServerFnError, ServerFnErrorErr}; +use crate::error::{FromServerFnError, IntoAppError, ServerFnErrorErr}; use bytes::Bytes; use futures::{Stream, TryStreamExt}; use reqwest::Response; @@ -7,22 +7,22 @@ use reqwest::Response; impl ClientRes for Response { async fn try_into_string(self) -> Result { self.text().await.map_err(|e| { - ServerFnErrorErr::Deserialization(e.to_string()).into() + ServerFnErrorErr::Deserialization(e.to_string()).into_app_error() }) } async fn try_into_bytes(self) -> Result { self.bytes().await.map_err(|e| { - ServerFnErrorErr::Deserialization(e.to_string()).into() + ServerFnErrorErr::Deserialization(e.to_string()).into_app_error() }) } fn try_into_stream( self, ) -> Result> + Send + 'static, E> { - Ok(self - .bytes_stream() - .map_err(|e| ServerFnErrorErr::Response(e.to_string()).into())) + Ok(self.bytes_stream().map_err(|e| { + ServerFnErrorErr::Response(e.to_string()).into_app_error() + })) } fn status(&self) -> u16 { From 4ea8c8a21e7305e4009dad6f6828b0e9962c4c43 Mon Sep 17 00:00:00 2001 From: Ryo Hirayama Date: Fri, 22 Nov 2024 18:14:07 +0900 Subject: [PATCH 10/15] Bring back FromStr/Display serialization --- leptos_server/src/action.rs | 5 +- server_fn/src/error.rs | 136 ++++++++++++++++++++++-------- server_fn/src/lib.rs | 2 +- server_fn/src/response/actix.rs | 2 +- server_fn/src/response/generic.rs | 2 +- server_fn/src/response/http.rs | 2 +- 6 files changed, 106 insertions(+), 43 deletions(-) diff --git a/leptos_server/src/action.rs b/leptos_server/src/action.rs index cbe81b189d..6d6dab63b1 100644 --- a/leptos_server/src/action.rs +++ b/leptos_server/src/action.rs @@ -3,10 +3,7 @@ use reactive_graph::{ owner::use_context, traits::DefinedAt, }; -use server_fn::{ - error::{FromServerFnError, ServerFnErrorSerde}, - ServerFn, -}; +use server_fn::{error::FromServerFnError, ServerFn}; use std::{ops::Deref, panic::Location, sync::Arc}; /// An error that can be caused by a server action. diff --git a/server_fn/src/error.rs b/server_fn/src/error.rs index bda864b94b..37ff9f552e 100644 --- a/server_fn/src/error.rs +++ b/server_fn/src/error.rs @@ -2,7 +2,10 @@ use base64::{engine::general_purpose::URL_SAFE, Engine as _}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::{fmt, fmt::Display, str::FromStr}; +use std::{ + fmt::{self, Display, Write}, + str::FromStr, +}; use thiserror::Error; use throw_error::Error; use url::Url; @@ -246,19 +249,6 @@ where } } -#[doc(hidden)] -/// An extension trait for types that can be serialized and deserialized to a [`String`]. -pub trait ServerFnErrorSerde: Sized { - /// The error type that can occur when serializing the custom error type. - type Error: std::error::Error; - - /// Converts the custom error type to a [`String`]. - fn ser(&self) -> Result; - - /// Deserializes the custom error type from a [`String`]. - fn de(data: &str) -> Self; -} - impl std::error::Error for ServerFnError where E: std::error::Error + 'static, @@ -318,7 +308,13 @@ pub enum ServerFnErrorErr { impl FromServerFnError for ServerFnError where - CustErr: std::fmt::Debug + Display + Serialize + DeserializeOwned + 'static, + CustErr: std::fmt::Debug + + Display + + Serialize + + DeserializeOwned + + 'static + + FromStr + + Display, { fn from_server_fn_error(value: ServerFnErrorErr) -> Self { match value { @@ -345,6 +341,74 @@ where ServerFnErrorErr::Response(value) => ServerFnError::Response(value), } } + + fn ser(&self) -> String { + let mut buf = String::new(); + let result = match self { + ServerFnError::WrappedServerError(e) => { + write!(&mut buf, "WrappedServerFn|{e}") + } + ServerFnError::Registration(e) => { + write!(&mut buf, "Registration|{e}") + } + ServerFnError::Request(e) => write!(&mut buf, "Request|{e}"), + ServerFnError::Response(e) => write!(&mut buf, "Response|{e}"), + ServerFnError::ServerError(e) => { + write!(&mut buf, "ServerError|{e}") + } + ServerFnError::MiddlewareError(e) => { + write!(&mut buf, "MiddlewareError|{e}") + } + ServerFnError::Deserialization(e) => { + write!(&mut buf, "Deserialization|{e}") + } + ServerFnError::Serialization(e) => { + write!(&mut buf, "Serialization|{e}") + } + ServerFnError::Args(e) => write!(&mut buf, "Args|{e}"), + ServerFnError::MissingArg(e) => { + write!(&mut buf, "MissingArg|{e}") + } + }; + match result { + Ok(()) => buf, + Err(_) => "Serialization|".to_string(), + } + } + + fn de(data: &str) -> Self { + data.split_once('|') + .and_then(|(ty, data)| match ty { + "WrappedServerFn" => match CustErr::from_str(data) { + Ok(d) => Some(ServerFnError::WrappedServerError(d)), + Err(_) => None, + }, + "Registration" => { + Some(ServerFnError::Registration(data.to_string())) + } + "Request" => Some(ServerFnError::Request(data.to_string())), + "Response" => Some(ServerFnError::Response(data.to_string())), + "ServerError" => { + Some(ServerFnError::ServerError(data.to_string())) + } + "Deserialization" => { + Some(ServerFnError::Deserialization(data.to_string())) + } + "Serialization" => { + Some(ServerFnError::Serialization(data.to_string())) + } + "Args" => Some(ServerFnError::Args(data.to_string())), + "MissingArg" => { + Some(ServerFnError::MissingArg(data.to_string())) + } + _ => None, + }) + .unwrap_or_else(|| { + ServerFnError::Deserialization(format!( + "Could not deserialize error {data:?}" + )) + }) + } } /// Associates a particular server function error with the server function @@ -384,10 +448,7 @@ impl ServerFnUrlError { let mut url = Url::parse(base)?; url.query_pairs_mut() .append_pair("__path", &self.path) - .append_pair( - "__err", - &URL_SAFE.encode(self.error.ser().unwrap_or_default()), - ); + .append_pair("__err", &URL_SAFE.encode(self.error.ser())); Ok(url) } @@ -465,10 +526,30 @@ impl std::error::Error for ServerFnErrorWrapper { /// A trait for types that can be returned from a server function. pub trait FromServerFnError: - Display + std::fmt::Debug + ServerFnErrorSerde + 'static + Display + std::fmt::Debug + Serialize + DeserializeOwned + 'static { /// Converts a [`ServerFnErrorErr`] into the application-specific custom error type. fn from_server_fn_error(value: ServerFnErrorErr) -> Self; + + /// Converts the custom error type to a [`String`]. Defaults to serializing to JSON. + fn ser(&self) -> String { + serde_json::to_string(self).unwrap_or_else(|e| { + serde_json::to_string(&Self::from_server_fn_error( + ServerFnErrorErr::Serialization(e.to_string()), + )) + .expect( + "error serializing should success at least with the \ + Serialization error", + ) + }) + } + + /// Deserializes the custom error type from a [`&str`]. Defaults to deserializing from JSON. + fn de(data: &str) -> Self { + serde_json::from_str(data).unwrap_or_else(|e| { + ServerFnErrorErr::Deserialization(e.to_string()).into_app_error() + }) + } } /// A helper trait for converting a [`ServerFnErrorErr`] into an application-specific custom error type that implements [`FromServerFnError`]. @@ -492,18 +573,3 @@ fn assert_from_server_fn_error_impl() { assert_impl::(); } - -impl ServerFnErrorSerde for E -where - E: Serialize + DeserializeOwned, -{ - type Error = serde_json::Error; - - fn ser(&self) -> Result { - serde_json::to_string(self) - } - - fn de(data: &str) -> Self { - serde_json::from_str(data).unwrap() - } -} diff --git a/server_fn/src/lib.rs b/server_fn/src/lib.rs index 9137310ac2..9f885d2f15 100644 --- a/server_fn/src/lib.rs +++ b/server_fn/src/lib.rs @@ -131,10 +131,10 @@ use codec::{Encoding, FromReq, FromRes, IntoReq, IntoRes}; #[doc(hidden)] pub use const_format; use dashmap::DashMap; +use error::FromServerFnError; pub use error::ServerFnError; #[cfg(feature = "form-redirects")] use error::ServerFnUrlError; -use error::{FromServerFnError, ServerFnErrorSerde}; use http::Method; use middleware::{Layer, Service}; use once_cell::sync::Lazy; diff --git a/server_fn/src/response/actix.rs b/server_fn/src/response/actix.rs index 8221877fd0..30496ff9c2 100644 --- a/server_fn/src/response/actix.rs +++ b/server_fn/src/response/actix.rs @@ -72,7 +72,7 @@ where ActixResponse(SendWrapper::new( HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR) .append_header((SERVER_FN_ERROR_HEADER, path)) - .body(err.ser().unwrap_or_else(|_| err.to_string())), + .body(err.ser()), )) } diff --git a/server_fn/src/response/generic.rs b/server_fn/src/response/generic.rs index f7909dcbf4..a78d5522a6 100644 --- a/server_fn/src/response/generic.rs +++ b/server_fn/src/response/generic.rs @@ -87,7 +87,7 @@ where Response::builder() .status(http::StatusCode::INTERNAL_SERVER_ERROR) .header(SERVER_FN_ERROR_HEADER, path) - .body(err.ser().unwrap_or_else(|_| err.to_string()).into()) + .body(err.ser().into()) .unwrap() } diff --git a/server_fn/src/response/http.rs b/server_fn/src/response/http.rs index 3d097c8729..79f5893e0f 100644 --- a/server_fn/src/response/http.rs +++ b/server_fn/src/response/http.rs @@ -53,7 +53,7 @@ where Response::builder() .status(http::StatusCode::INTERNAL_SERVER_ERROR) .header(SERVER_FN_ERROR_HEADER, path) - .body(err.ser().unwrap_or_else(|_| err.to_string()).into()) + .body(err.ser().into()) .unwrap() } From 4b0a53f169579c10e024e7d9a3d234fbd3abf452 Mon Sep 17 00:00:00 2001 From: Ryo Hirayama Date: Fri, 22 Nov 2024 18:30:06 +0900 Subject: [PATCH 11/15] Fix doc --- server_fn/src/codec/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server_fn/src/codec/mod.rs b/server_fn/src/codec/mod.rs index 33676cfb7f..6b029f12ee 100644 --- a/server_fn/src/codec/mod.rs +++ b/server_fn/src/codec/mod.rs @@ -82,7 +82,7 @@ pub use stream::*; /// ) -> Result { /// // try to serialize the data /// let data = serde_json::to_string(&self) -/// .map_err(|e| ServerFnErrorErr::Serialization(e.to_string()).into())?; +/// .map_err(|e| ServerFnErrorErr::Serialization(e.to_string()).into_app_error())?; /// // and use it as the body of a POST request /// Request::try_new_post(path, accepts, Json::CONTENT_TYPE, data) /// } @@ -119,7 +119,7 @@ pub trait IntoReq { /// let string_data = req.try_into_string().await?; /// // deserialize the data /// serde_json::from_str(&string_data) -/// .map_err(|e| ServerFnErrorErr::Args(e.to_string()).into()) +/// .map_err(|e| ServerFnErrorErr::Args(e.to_string()).into_app_error()) /// } /// } /// ``` @@ -187,7 +187,7 @@ pub trait IntoRes { /// let data = res.try_into_string().await?; /// // and tries to deserialize it as JSON /// serde_json::from_str(&data) -/// .map_err(|e| ServerFnErrorErr::Deserialization(e.to_string()).into()) +/// .map_err(|e| ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()) /// } /// } /// ``` From c7ec697f11f9aaf8cb5e506121030e1428b30326 Mon Sep 17 00:00:00 2001 From: Ryo Hirayama Date: Fri, 22 Nov 2024 18:41:48 +0900 Subject: [PATCH 12/15] Move items for better diff --- server_fn/src/error.rs | 114 ++++++++++++++++++++--------------------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/server_fn/src/error.rs b/server_fn/src/error.rs index 37ff9f552e..881e656bcf 100644 --- a/server_fn/src/error.rs +++ b/server_fn/src/error.rs @@ -249,63 +249,6 @@ where } } -impl std::error::Error for ServerFnError -where - E: std::error::Error + 'static, - ServerFnError: std::fmt::Display, -{ - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - ServerFnError::WrappedServerError(e) => Some(e), - _ => None, - } - } -} - -/// Type for errors that can occur when using server functions. If you need to return a custom error type from a server function, implement `From` for your custom error type. -/// -/// Unlike [`ServerFnError`], 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, PartialEq, Eq, Serialize, Deserialize)] -#[cfg_attr( - feature = "rkyv", - derive(rkyv::Archive, rkyv::Serialize, rkyv::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 when there is an error while actually running the middleware on the server. - #[error("error running middleware: {0}")] - MiddlewareError(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), - /// Occurs on the server if there is an error creating an HTTP response. - #[error("error creating response {0}")] - Response(String), -} - impl FromServerFnError for ServerFnError where CustErr: std::fmt::Debug @@ -411,6 +354,63 @@ where } } +impl std::error::Error for ServerFnError +where + E: std::error::Error + 'static, + ServerFnError: std::fmt::Display, +{ + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + ServerFnError::WrappedServerError(e) => Some(e), + _ => None, + } + } +} + +/// Type for errors that can occur when using server functions. If you need to return a custom error type from a server function, implement `From` for your custom error type. +/// +/// Unlike [`ServerFnError`], 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, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Serialize, rkyv::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 when there is an error while actually running the middleware on the server. + #[error("error running middleware: {0}")] + MiddlewareError(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), + /// Occurs on the server if there is an error creating an HTTP response. + #[error("error creating response {0}")] + Response(String), +} + /// Associates a particular server function error with the server function /// found at a particular path. /// From f3a447093f4ed3be1725c08f9ac23836cc30e224 Mon Sep 17 00:00:00 2001 From: Ryo Hirayama Date: Fri, 22 Nov 2024 18:48:00 +0900 Subject: [PATCH 13/15] Update doc --- server_fn/src/error.rs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/server_fn/src/error.rs b/server_fn/src/error.rs index 881e656bcf..22bd35c6e0 100644 --- a/server_fn/src/error.rs +++ b/server_fn/src/error.rs @@ -155,8 +155,8 @@ impl ViaError for WrapError { } } -/// Type for errors that can occur when using server functions. -/// This type is intended to be used as the return type of the server function for easy error conversion with `?` operator. +/// A type that can be used as the return type of the server function for easy error conversion with `?` operator. +/// This type can be replaced with any other error type that implements `FromServerFnError`. /// /// Unlike [`ServerFnErrorErr`], this does not implement [`Error`](trait@std::error::Error). /// This means that other error types can easily be converted into it using the @@ -367,15 +367,7 @@ where } } -/// Type for errors that can occur when using server functions. If you need to return a custom error type from a server function, implement `From` for your custom error type. -/// -/// Unlike [`ServerFnError`], 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. +/// Type for errors that can occur when using server functions. If you need to return a custom error type from a server function, implement `FromServerFnError` for your custom error type. #[derive(Error, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[cfg_attr( feature = "rkyv", From 957860732c5001a9c171969abba23340cea07c8a Mon Sep 17 00:00:00 2001 From: Ryo Hirayama Date: Fri, 22 Nov 2024 23:21:32 +0900 Subject: [PATCH 14/15] Fix not to use err.to_string for error encoding --- integrations/actix/src/lib.rs | 1 - server_fn/src/codec/cbor.rs | 4 +- server_fn/src/codec/json.rs | 6 +-- server_fn/src/codec/msgpack.rs | 4 +- server_fn/src/codec/multipart.rs | 2 +- server_fn/src/codec/postcard.rs | 4 +- server_fn/src/codec/rkyv.rs | 9 ++-- server_fn/src/codec/serde_lite.rs | 4 +- server_fn/src/codec/stream.rs | 6 +-- server_fn/src/error.rs | 6 +-- server_fn/src/lib.rs | 66 +++++++++++++++-------------- server_fn/src/middleware/mod.rs | 69 +++++++++++++++++++------------ server_fn/src/response/actix.rs | 10 +++-- server_fn/src/response/generic.rs | 10 +++-- server_fn/src/response/http.rs | 10 +++-- server_fn/src/response/mod.rs | 19 +++++---- 16 files changed, 130 insertions(+), 100 deletions(-) diff --git a/integrations/actix/src/lib.rs b/integrations/actix/src/lib.rs index d4ca97514e..a6b70ed438 100644 --- a/integrations/actix/src/lib.rs +++ b/integrations/actix/src/lib.rs @@ -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(), diff --git a/server_fn/src/codec/cbor.rs b/server_fn/src/codec/cbor.rs index 851b68b939..d9952a0a48 100644 --- a/server_fn/src/codec/cbor.rs +++ b/server_fn/src/codec/cbor.rs @@ -2,7 +2,7 @@ use super::{Encoding, FromReq, FromRes, IntoReq, IntoRes}; use crate::{ error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::{ClientReq, Req}, - response::{ClientRes, Res}, + response::{ClientRes, TryRes}, }; use bytes::Bytes; use http::Method; @@ -51,7 +51,7 @@ where impl IntoRes for T where - Response: Res, + Response: TryRes, T: Serialize + Send, E: FromServerFnError, { diff --git a/server_fn/src/codec/json.rs b/server_fn/src/codec/json.rs index a0cf2fed95..88e9f98842 100644 --- a/server_fn/src/codec/json.rs +++ b/server_fn/src/codec/json.rs @@ -2,7 +2,7 @@ use super::{Encoding, FromReq, FromRes, Streaming}; use crate::{ error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::{ClientReq, Req}, - response::{ClientRes, Res}, + response::{ClientRes, TryRes}, IntoReq, IntoRes, }; use bytes::Bytes; @@ -47,7 +47,7 @@ where impl IntoRes for T where - Response: Res, + Response: TryRes, T: Serialize + Send, E: FromServerFnError, { @@ -184,7 +184,7 @@ where impl IntoRes for JsonStream where - Response: Res, + Response: TryRes, T: Serialize + 'static, E: FromServerFnError, { diff --git a/server_fn/src/codec/msgpack.rs b/server_fn/src/codec/msgpack.rs index 3d13c9235d..339137f84a 100644 --- a/server_fn/src/codec/msgpack.rs +++ b/server_fn/src/codec/msgpack.rs @@ -2,7 +2,7 @@ use super::{Encoding, FromReq, FromRes, IntoReq, IntoRes}; use crate::{ error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::{ClientReq, Req}, - response::{ClientRes, Res}, + response::{ClientRes, TryRes}, }; use bytes::Bytes; use http::Method; @@ -50,7 +50,7 @@ where impl IntoRes for T where - Response: Res, + Response: TryRes, T: Serialize + Send, E: FromServerFnError, { diff --git a/server_fn/src/codec/multipart.rs b/server_fn/src/codec/multipart.rs index f1ddb98117..75e8921b6b 100644 --- a/server_fn/src/codec/multipart.rs +++ b/server_fn/src/codec/multipart.rs @@ -84,7 +84,7 @@ where .expect("couldn't parse boundary"); let stream = req.try_into_stream()?; let data = multer::Multipart::new( - stream.map(|data| data.map_err(|e| e.to_string())), + stream.map(|data| data.map_err(|e| e.ser())), boundary, ); Ok(MultipartData::Server(data).into()) diff --git a/server_fn/src/codec/postcard.rs b/server_fn/src/codec/postcard.rs index 6e07c8b50b..f1f4ede4e8 100644 --- a/server_fn/src/codec/postcard.rs +++ b/server_fn/src/codec/postcard.rs @@ -2,7 +2,7 @@ use super::{Encoding, FromReq, FromRes, IntoReq, IntoRes}; use crate::{ error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::{ClientReq, Req}, - response::{ClientRes, Res}, + response::{ClientRes, TryRes}, }; use bytes::Bytes; use http::Method; @@ -50,7 +50,7 @@ where impl IntoRes for T where - Response: Res, + Response: TryRes, T: Serialize + Send, E: FromServerFnError, { diff --git a/server_fn/src/codec/rkyv.rs b/server_fn/src/codec/rkyv.rs index d45087d9b9..8cfebca964 100644 --- a/server_fn/src/codec/rkyv.rs +++ b/server_fn/src/codec/rkyv.rs @@ -2,7 +2,7 @@ use super::{Encoding, FromReq, FromRes, IntoReq, IntoRes}; use crate::{ error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::{ClientReq, Req}, - response::{ClientRes, Res}, + response::{ClientRes, TryRes}, }; use bytes::Bytes; use futures::StreamExt; @@ -60,10 +60,7 @@ where while let Some(chunk) = body_stream.next().await { match chunk { Err(e) => { - return Err(ServerFnErrorErr::Deserialization( - e.to_string(), - ) - .into_app_error()); + return Err(e); } Ok(bytes) => { for byte in bytes { @@ -79,7 +76,7 @@ where impl IntoRes for T where - Response: Res, + Response: TryRes, T: Send, T: Archive + for<'a> Serialize>, T::Archived: Deserialize diff --git a/server_fn/src/codec/serde_lite.rs b/server_fn/src/codec/serde_lite.rs index b0c2cc4be4..e5bb4e7271 100644 --- a/server_fn/src/codec/serde_lite.rs +++ b/server_fn/src/codec/serde_lite.rs @@ -2,7 +2,7 @@ use super::{Encoding, FromReq, FromRes}; use crate::{ error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::{ClientReq, Req}, - response::{ClientRes, Res}, + response::{ClientRes, TryRes}, IntoReq, IntoRes, }; use http::Method; @@ -49,7 +49,7 @@ where impl IntoRes for T where - Response: Res, + Response: TryRes, T: Serialize + Send, E: FromServerFnError, { diff --git a/server_fn/src/codec/stream.rs b/server_fn/src/codec/stream.rs index 977a22c8bf..5951aeef66 100644 --- a/server_fn/src/codec/stream.rs +++ b/server_fn/src/codec/stream.rs @@ -2,7 +2,7 @@ use super::{Encoding, FromReq, FromRes, IntoReq}; use crate::{ error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::{ClientReq, Req}, - response::{ClientRes, Res}, + response::{ClientRes, TryRes}, IntoRes, ServerFnError, }; use bytes::Bytes; @@ -102,7 +102,7 @@ where impl IntoRes for ByteStream where - Response: Res, + Response: TryRes, E: 'static, { async fn into_res(self) -> Result { @@ -226,7 +226,7 @@ where impl IntoRes for TextStream where - Response: Res, + Response: TryRes, E: 'static, { async fn into_res(self) -> Result { diff --git a/server_fn/src/error.rs b/server_fn/src/error.rs index 22bd35c6e0..432833b5f0 100644 --- a/server_fn/src/error.rs +++ b/server_fn/src/error.rs @@ -502,11 +502,11 @@ impl From>> for ServerFnError { #[derive(Debug)] #[doc(hidden)] /// Only used instantly only when a framework needs E: Error. -pub struct ServerFnErrorWrapper(pub E); +pub struct ServerFnErrorWrapper(pub E); impl Display for ServerFnErrorWrapper { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) + write!(f, "{}", self.0.ser()) } } @@ -518,7 +518,7 @@ impl std::error::Error for ServerFnErrorWrapper { /// A trait for types that can be returned from a server function. pub trait FromServerFnError: - Display + std::fmt::Debug + Serialize + DeserializeOwned + 'static + std::fmt::Debug + Serialize + DeserializeOwned + 'static { /// Converts a [`ServerFnErrorErr`] into the application-specific custom error type. fn from_server_fn_error(value: ServerFnErrorErr) -> Self; diff --git a/server_fn/src/lib.rs b/server_fn/src/lib.rs index 9f885d2f15..1c3132ee18 100644 --- a/server_fn/src/lib.rs +++ b/server_fn/src/lib.rs @@ -131,16 +131,16 @@ use codec::{Encoding, FromReq, FromRes, IntoReq, IntoRes}; #[doc(hidden)] pub use const_format; use dashmap::DashMap; -use error::FromServerFnError; pub use error::ServerFnError; #[cfg(feature = "form-redirects")] use error::ServerFnUrlError; +use error::{FromServerFnError, ServerFnErrorErr}; use http::Method; -use middleware::{Layer, Service}; +use middleware::{BoxedService, Layer, Service}; use once_cell::sync::Lazy; use redirect::RedirectHook; use request::Req; -use response::{ClientRes, Res}; +use response::{ClientRes, Res, TryRes}; #[cfg(feature = "rkyv")] pub use rkyv; #[doc(hidden)] @@ -203,7 +203,7 @@ where type ServerRequest: Req + Send; /// The type of the HTTP response returned by the server function on the server side. - type ServerResponse: Res + Send; + type ServerResponse: Res + TryRes + Send; /// The return type of the server function. /// @@ -264,7 +264,10 @@ where .map(|res| (res, None)) .unwrap_or_else(|e| { ( - Self::ServerResponse::error_response(Self::PATH, &e), + Self::ServerResponse::error_response( + Self::PATH, + e.ser(), + ), Some(e), ) }); @@ -383,21 +386,20 @@ pub struct ServerFnTraitObj { method: Method, handler: fn(Req) -> Pin + Send>>, middleware: fn() -> MiddlewareSet, + ser: fn(ServerFnErrorErr) -> String, } impl ServerFnTraitObj { /// Converts the relevant parts of a server function into a trait object. - pub const fn new( - path: &'static str, - method: Method, + pub const fn new>( handler: fn(Req) -> Pin + Send>>, - middleware: fn() -> MiddlewareSet, ) -> Self { Self { - path, - method, + path: S::PATH, + method: S::InputEncoding::METHOD, handler, - middleware, + middleware: S::middlewares, + ser: |e| S::Error::from_server_fn_error(e).ser(), } } @@ -420,6 +422,16 @@ impl ServerFnTraitObj { pub fn middleware(&self) -> MiddlewareSet { (self.middleware)() } + + /// Converts the server function into a boxed service. + pub fn boxed(self) -> BoxedService + where + Self: Service, + Req: 'static, + Res: 'static, + { + BoxedService::new(self.ser, self) + } } impl Service for ServerFnTraitObj @@ -427,7 +439,11 @@ where Req: Send + 'static, Res: 'static, { - fn run(&mut self, req: Req) -> Pin + Send>> { + fn run( + &mut self, + req: Req, + _ser: fn(ServerFnErrorErr) -> String, + ) -> Pin + Send>> { let handler = self.handler; Box::pin(async move { handler(req).await }) } @@ -440,6 +456,7 @@ impl Clone for ServerFnTraitObj { method: self.method.clone(), handler: self.handler, middleware: self.middleware, + ser: self.ser, } } } @@ -463,8 +480,8 @@ impl inventory::Collect #[cfg(feature = "axum-no-default")] pub mod axum { use crate::{ - middleware::{BoxedService, Service}, - Encoding, LazyServerFnMap, ServerFn, ServerFnTraitObj, + middleware::BoxedService, Encoding, LazyServerFnMap, ServerFn, + ServerFnTraitObj, }; use axum::body::Body; use http::{Method, Request, Response, StatusCode}; @@ -486,12 +503,7 @@ pub mod axum { { REGISTERED_SERVER_FUNCTIONS.insert( (T::PATH.into(), T::InputEncoding::METHOD), - ServerFnTraitObj::new( - T::PATH, - T::InputEncoding::METHOD, - |req| Box::pin(T::run_on_server(req)), - T::middlewares, - ), + ServerFnTraitObj::new::(|req| Box::pin(T::run_on_server(req))), ); } @@ -535,7 +547,7 @@ pub mod axum { let key = (path.into(), method); REGISTERED_SERVER_FUNCTIONS.get(&key).map(|server_fn| { let middleware = (server_fn.middleware)(); - let mut service = BoxedService::new(server_fn.clone()); + let mut service = server_fn.clone().boxed(); for middleware in middleware { service = middleware.layer(service); } @@ -574,12 +586,7 @@ pub mod actix { { REGISTERED_SERVER_FUNCTIONS.insert( (T::PATH.into(), T::InputEncoding::METHOD), - ServerFnTraitObj::new( - T::PATH, - T::InputEncoding::METHOD, - |req| Box::pin(T::run_on_server(req)), - T::middlewares, - ), + ServerFnTraitObj::new::(|req| Box::pin(T::run_on_server(req))), ); } @@ -599,7 +606,6 @@ pub mod actix { let method = req.method(); if let Some(mut service) = get_server_fn_service(path, method) { service - .0 .run(ActixRequest::from((req, payload))) .await .0 @@ -640,7 +646,7 @@ pub mod actix { REGISTERED_SERVER_FUNCTIONS.get(&(path.into(), method)).map( |server_fn| { let middleware = (server_fn.middleware)(); - let mut service = BoxedService::new(server_fn.clone()); + let mut service = server_fn.clone().boxed(); for middleware in middleware { service = middleware.layer(service); } diff --git a/server_fn/src/middleware/mod.rs b/server_fn/src/middleware/mod.rs index deeeb46ce5..2c96ded6bb 100644 --- a/server_fn/src/middleware/mod.rs +++ b/server_fn/src/middleware/mod.rs @@ -1,3 +1,4 @@ +use crate::error::ServerFnErrorErr; use std::{future::Future, pin::Pin}; /// An abstraction over a middleware layer, which can be used to add additional @@ -8,12 +9,31 @@ pub trait Layer: Send + Sync + 'static { } /// A type-erased service, which takes an HTTP request and returns a response. -pub struct BoxedService(pub Box + Send>); +pub struct BoxedService { + /// A function that converts a [`ServerFnErrorErr`] into a string. + pub ser: fn(ServerFnErrorErr) -> String, + /// The inner service. + pub service: Box + Send>, +} impl BoxedService { /// Constructs a type-erased service from this service. - pub fn new(service: impl Service + Send + 'static) -> Self { - Self(Box::new(service)) + pub fn new( + ser: fn(ServerFnErrorErr) -> String, + service: impl Service + Send + 'static, + ) -> Self { + Self { + ser, + service: Box::new(service), + } + } + + /// Converts a request into a response by running the inner service. + pub fn run( + &mut self, + req: Req, + ) -> Pin + Send>> { + self.service.run(req, self.ser) } } @@ -23,17 +43,14 @@ pub trait Service { fn run( &mut self, req: Request, + ser: fn(ServerFnErrorErr) -> String, ) -> Pin + Send>>; } #[cfg(feature = "axum-no-default")] mod axum { use super::{BoxedService, Service}; - use crate::{ - error::{FromServerFnError, ServerFnErrorErr}, - response::Res, - ServerFnError, - }; + use crate::{error::ServerFnErrorErr, response::Res, ServerFnError}; use axum::body::Body; use http::{Request, Response}; use std::{future::Future, pin::Pin}; @@ -42,20 +59,20 @@ mod axum { where S: tower::Service, Response = Response>, S::Future: Send + 'static, - S::Error: FromServerFnError + Send + Sync + 'static, + S::Error: std::fmt::Display + Send + 'static, { fn run( &mut self, req: Request, + ser: fn(ServerFnErrorErr) -> String, ) -> Pin> + Send>> { let path = req.uri().path().to_string(); let inner = self.call(req); Box::pin(async move { inner.await.unwrap_or_else(|e| { - let err = S::Error::from_server_fn_error( - ServerFnErrorErr::MiddlewareError(e.to_string()), - ); - Response::::error_response(&path, &err) + let err = + ser(ServerFnErrorErr::MiddlewareError(e.to_string())); + Response::::error_response(&path, err) }) }) } @@ -82,7 +99,7 @@ mod axum { } fn call(&mut self, req: Request) -> Self::Future { - let inner = self.0.run(req); + let inner = self.service.run(req, self.ser); Box::pin(async move { Ok(inner.await) }) } } @@ -99,7 +116,7 @@ mod axum { &self, inner: BoxedService, Response>, ) -> BoxedService, Response> { - BoxedService(Box::new(self.layer(inner))) + BoxedService::new(inner.ser, self.layer(inner)) } } } @@ -107,7 +124,7 @@ mod axum { #[cfg(feature = "actix")] mod actix { use crate::{ - error::{FromServerFnError, ServerFnErrorErr}, + error::ServerFnErrorErr, request::actix::ActixRequest, response::{actix::ActixResponse, Res}, }; @@ -118,20 +135,20 @@ mod actix { where S: actix_web::dev::Service, S::Future: Send + 'static, - S::Error: FromServerFnError, + S::Error: std::fmt::Display + Send + 'static, { fn run( &mut self, req: HttpRequest, + ser: fn(ServerFnErrorErr) -> String, ) -> Pin + Send>> { let path = req.uri().path().to_string(); let inner = self.call(req); Box::pin(async move { inner.await.unwrap_or_else(|e| { - let err = S::Error::from_server_fn_error( - ServerFnErrorErr::MiddlewareError(e.to_string()), - ); - ActixResponse::error_response(&path, &err).take() + let err = + ser(ServerFnErrorErr::MiddlewareError(e.to_string())); + ActixResponse::error_response(&path, err).take() }) }) } @@ -141,20 +158,20 @@ mod actix { where S: actix_web::dev::Service, S::Future: Send + 'static, - S::Error: FromServerFnError, + S::Error: std::fmt::Display + Send + 'static, { fn run( &mut self, req: ActixRequest, + ser: fn(ServerFnErrorErr) -> String, ) -> Pin + Send>> { let path = req.0 .0.uri().path().to_string(); let inner = self.call(req.0.take().0); Box::pin(async move { ActixResponse::from(inner.await.unwrap_or_else(|e| { - let err = S::Error::from_server_fn_error( - ServerFnErrorErr::MiddlewareError(e.to_string()), - ); - ActixResponse::error_response(&path, &err).take() + let err = + ser(ServerFnErrorErr::MiddlewareError(e.to_string())); + ActixResponse::error_response(&path, err).take() })) }) } diff --git a/server_fn/src/response/actix.rs b/server_fn/src/response/actix.rs index 30496ff9c2..3a168b3dad 100644 --- a/server_fn/src/response/actix.rs +++ b/server_fn/src/response/actix.rs @@ -1,4 +1,4 @@ -use super::Res; +use super::{Res, TryRes}; use crate::error::{ FromServerFnError, ServerFnErrorWrapper, SERVER_FN_ERROR_HEADER, }; @@ -34,7 +34,7 @@ impl From for ActixResponse { } } -impl Res for ActixResponse +impl TryRes for ActixResponse where E: FromServerFnError, { @@ -67,12 +67,14 @@ where .streaming(data.map(|data| data.map_err(ServerFnErrorWrapper))), ))) } +} - fn error_response(path: &str, err: &E) -> Self { +impl Res for ActixResponse { + fn error_response(path: &str, err: String) -> Self { ActixResponse(SendWrapper::new( HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR) .append_header((SERVER_FN_ERROR_HEADER, path)) - .body(err.ser()), + .body(err), )) } diff --git a/server_fn/src/response/generic.rs b/server_fn/src/response/generic.rs index a78d5522a6..cdb06093fb 100644 --- a/server_fn/src/response/generic.rs +++ b/server_fn/src/response/generic.rs @@ -12,7 +12,7 @@ //! * `wasm32-wasip*` integration crate `leptos_wasi` is using this //! crate under the hood. -use super::Res; +use super::{Res, TryRes}; use crate::error::{ FromServerFnError, IntoAppError, ServerFnErrorErr, ServerFnErrorWrapper, SERVER_FN_ERROR_HEADER, @@ -41,7 +41,7 @@ impl From for Body { } } -impl Res for Response +impl TryRes for Response where E: Send + Sync + FromServerFnError, { @@ -82,12 +82,14 @@ where ServerFnErrorErr::Response(e.to_string()).into_app_error() }) } +} - fn error_response(path: &str, err: &E) -> Self { +impl Res for Response { + fn error_response(path: &str, err: String) -> Self { Response::builder() .status(http::StatusCode::INTERNAL_SERVER_ERROR) .header(SERVER_FN_ERROR_HEADER, path) - .body(err.ser().into()) + .body(err.into()) .unwrap() } diff --git a/server_fn/src/response/http.rs b/server_fn/src/response/http.rs index 79f5893e0f..15caa5b95d 100644 --- a/server_fn/src/response/http.rs +++ b/server_fn/src/response/http.rs @@ -1,4 +1,4 @@ -use super::Res; +use super::{Res, TryRes}; use crate::error::{ FromServerFnError, IntoAppError, ServerFnErrorErr, ServerFnErrorWrapper, SERVER_FN_ERROR_HEADER, @@ -8,7 +8,7 @@ use bytes::Bytes; use futures::{Stream, TryStreamExt}; use http::{header, HeaderValue, Response, StatusCode}; -impl Res for Response +impl TryRes for Response where E: Send + Sync + FromServerFnError, { @@ -48,12 +48,14 @@ where ServerFnErrorErr::Response(e.to_string()).into_app_error() }) } +} - fn error_response(path: &str, err: &E) -> Self { +impl Res for Response { + fn error_response(path: &str, err: String) -> Self { Response::builder() .status(http::StatusCode::INTERNAL_SERVER_ERROR) .header(SERVER_FN_ERROR_HEADER, path) - .body(err.ser().into()) + .body(err.into()) .unwrap() } diff --git a/server_fn/src/response/mod.rs b/server_fn/src/response/mod.rs index b9e212e1be..f479dbaa81 100644 --- a/server_fn/src/response/mod.rs +++ b/server_fn/src/response/mod.rs @@ -18,7 +18,7 @@ use futures::Stream; use std::future::Future; /// Represents the response as created by the server; -pub trait Res +pub trait TryRes where Self: Sized, { @@ -33,9 +33,12 @@ where content_type: &str, data: impl Stream> + Send + 'static, ) -> Result; +} +/// Represents the response as created by the server; +pub trait Res { /// Converts an error into a response, with a `500` status code and the error text as its body. - fn error_response(path: &str, err: &E) -> Self; + fn error_response(path: &str, err: String) -> Self; /// Redirect the response by setting a 302 code and Location header. fn redirect(&mut self, path: &str); @@ -75,7 +78,7 @@ pub trait ClientRes { /// server response type when compiling for the client. pub struct BrowserMockRes; -impl Res for BrowserMockRes { +impl TryRes for BrowserMockRes { fn try_from_string(_content_type: &str, _data: String) -> Result { unreachable!() } @@ -84,16 +87,18 @@ impl Res for BrowserMockRes { unreachable!() } - fn error_response(_path: &str, _err: &E) -> Self { - unreachable!() - } - fn try_from_stream( _content_type: &str, _data: impl Stream>, ) -> Result { unreachable!() } +} + +impl Res for BrowserMockRes { + fn error_response(_path: &str, _err: String) -> Self { + unreachable!() + } fn redirect(&mut self, _path: &str) { unreachable!() From 60befc33afe53cbd908facb68209dc191dc56d47 Mon Sep 17 00:00:00 2001 From: Ryo Hirayama Date: Sat, 23 Nov 2024 09:05:19 +0900 Subject: [PATCH 15/15] Fix server_fns_axum example CI --- examples/server_fns_axum/src/app.rs | 4 ++-- integrations/axum/src/lib.rs | 2 -- server_fn_macro/src/lib.rs | 9 ++------- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/examples/server_fns_axum/src/app.rs b/examples/server_fns_axum/src/app.rs index 2cfce8234d..2dc26bacc3 100644 --- a/examples/server_fns_axum/src/app.rs +++ b/examples/server_fns_axum/src/app.rs @@ -11,7 +11,7 @@ use server_fn::{ }, 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")] @@ -796,7 +796,7 @@ where impl IntoRes for TomlEncoded where - Response: Res, + Response: TryRes, T: Serialize + Send, Err: FromServerFnError, { diff --git a/integrations/axum/src/lib.rs b/integrations/axum/src/lib.rs index ffde4c2bd3..a1185fcd0f 100644 --- a/integrations/axum/src/lib.rs +++ b/integrations/axum/src/lib.rs @@ -369,8 +369,6 @@ async fn handle_server_fns_inner( additional_context: impl Fn() + 'static + Clone + Send, req: Request, ) -> 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); diff --git a/server_fn_macro/src/lib.rs b/server_fn_macro/src/lib.rs index b7e477fdce..b904f97a5d 100644 --- a/server_fn_macro/src/lib.rs +++ b/server_fn_macro/src/lib.rs @@ -368,13 +368,8 @@ pub fn server_macro_impl( quote! { #server_fn_path::inventory::submit! {{ use #server_fn_path::{ServerFn, codec::Encoding}; - #server_fn_path::ServerFnTraitObj::new( - #wrapped_struct_name_turbofish::PATH, - <#wrapped_struct_name as ServerFn>::InputEncoding::METHOD, - |req| { - Box::pin(#wrapped_struct_name_turbofish::run_on_server(req)) - }, - #wrapped_struct_name_turbofish::middlewares + #server_fn_path::ServerFnTraitObj::new::<#wrapped_struct_name>( + |req| Box::pin(#wrapped_struct_name_turbofish::run_on_server(req)), ) }} }