diff --git a/Cargo.lock b/Cargo.lock index 668e99cb9f..85448dd8c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3278,6 +3278,7 @@ version = "0.7.4" dependencies = [ "actix-web", "axum", + "base64", "bytes", "ciborium", "const_format", diff --git a/examples/server_fns_axum/src/app.rs b/examples/server_fns_axum/src/app.rs index 75f5c8fe79..2dc26bacc3 100644 --- a/examples/server_fns_axum/src/app.rs +++ b/examples/server_fns_axum/src/app.rs @@ -9,8 +9,9 @@ use server_fn::{ MultipartFormData, Postcard, Rkyv, SerdeLite, StreamingText, TextStream, }, + error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::{browser::BrowserRequest, ClientReq, Req}, - response::{browser::BrowserResponse, ClientRes, Res}, + response::{browser::BrowserResponse, ClientRes, TryRes}, }; use std::future::Future; #[cfg(feature = "ssr")] @@ -652,32 +653,72 @@ pub fn FileWatcher() -> impl IntoView { /// implementations if you'd like. However, it's much lighter weight to use something like `strum` /// simply to generate those trait implementations. #[server] -pub async fn ascii_uppercase( - text: String, -) -> Result> { +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, EnumString, Display)] +#[derive(Debug, Clone, Display, EnumString, Serialize, Deserialize)] pub enum InvalidArgument { TooShort, TooLong, NotAscii, } +#[derive(Debug, Clone, Display, Serialize, Deserialize)] +pub enum MyErrors { + InvalidArgument(InvalidArgument), + ServerFnError(ServerFnErrorErr), + Other(String), +} + +impl From for MyErrors { + fn from(value: InvalidArgument) -> Self { + MyErrors::InvalidArgument(value) + } +} + +impl From for MyErrors { + fn from(value: String) -> Self { + MyErrors::Other(value) + } +} + +impl FromServerFnError for MyErrors { + fn from_server_fn_error(value: ServerFnErrorErr) -> Self { + MyErrors::ServerFnError(value) + } +} + #[component] 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

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

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

+

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

} } @@ -726,14 +770,12 @@ impl IntoReq for TomlEncoded where Request: ClientReq, T: Serialize, + Err: FromServerFnError, { - fn into_req( - self, - path: &str, - accepts: &str, - ) -> Result> { - let data = toml::to_string(&self.0) - .map_err(|e| ServerFnError::Serialization(e.to_string()))?; + fn into_req(self, path: &str, accepts: &str) -> Result { + 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) } } @@ -742,23 +784,26 @@ 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_app_error()) } } impl IntoRes for TomlEncoded where - Response: Res, + Response: TryRes, T: Serialize + Send, + Err: FromServerFnError, { - async fn into_res(self) -> Result> { - let data = toml::to_string(&self.0) - .map_err(|e| ServerFnError::Serialization(e.to_string()))?; + async fn into_res(self) -> Result { + 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) } } @@ -767,12 +812,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_app_error() + }) } } @@ -835,7 +881,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 +893,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/integrations/actix/src/lib.rs b/integrations/actix/src/lib.rs index bdf9d61a9d..b12cabb45f 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/integrations/axum/src/lib.rs b/integrations/axum/src/lib.rs index 80bc2da22b..0cb0dbbed1 100644 --- a/integrations/axum/src/lib.rs +++ b/integrations/axum/src/lib.rs @@ -368,8 +368,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/leptos/src/form.rs b/leptos/src/form.rs index 37c483aba4..a29eaf8a9e 100644 --- a/leptos/src/form.rs +++ b/leptos/src/form.rs @@ -3,7 +3,11 @@ use leptos_dom::helpers::window; use leptos_server::{ServerAction, ServerMultiAction}; use serde::de::DeserializeOwned; use server_fn::{ - client::Client, codec::PostUrl, request::ClientReq, ServerFn, ServerFnError, + client::Client, + codec::PostUrl, + error::{IntoAppError, ServerFnErrorErr}, + request::ClientReq, + ServerFn, }; use tachys::{ either::Either, @@ -121,9 +125,10 @@ where "Error converting form field into server function \ arguments: {err:?}" ); - value.set(Some(Err(ServerFnError::Serialization( + value.set(Some(Err(ServerFnErrorErr::Serialization( err.to_string(), - )))); + ) + .into_app_error()))); version.update(|n| *n += 1); } } @@ -187,9 +192,10 @@ where action.dispatch(new_input); } Err(err) => { - action.dispatch_sync(Err(ServerFnError::Serialization( + action.dispatch_sync(Err(ServerFnErrorErr::Serialization( err.to_string(), - ))); + ) + .into_app_error())); } } }; diff --git a/leptos/src/lib.rs b/leptos/src/lib.rs index 57da2bafad..775da9271e 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/leptos_server/src/action.rs b/leptos_server/src/action.rs index c66e751174..34ae0a2087 100644 --- a/leptos_server/src/action.rs +++ b/leptos_server/src/action.rs @@ -3,7 +3,7 @@ use reactive_graph::{ owner::use_context, traits::DefinedAt, }; -use server_fn::{error::ServerFnErrorSerde, ServerFn, ServerFnError}; +use server_fn::{error::FromServerFnError, ServerFn}; use std::{ops::Deref, panic::Location, sync::Arc}; /// An error that can be caused by a server action. @@ -42,7 +42,7 @@ where S: ServerFn + 'static, S::Output: 'static, { - inner: ArcAction>>, + inner: ArcAction>, #[cfg(any(debug_assertions, leptos_debuginfo))] defined_at: &'static Location<'static>, } @@ -52,13 +52,14 @@ where S: ServerFn + Clone + Send + Sync + 'static, S::Output: Send + Sync + 'static, S::Error: Send + Sync + 'static, + S::Error: FromServerFnError, { /// Creates a new [`ArcAction`] that will call the server function `S` when dispatched. #[track_caller] pub fn new() -> Self { let err = use_context::().and_then(|error| { (error.path() == S::PATH) - .then(|| ServerFnError::::de(error.err())) + .then(|| S::Error::de(error.err())) .map(Err) }); Self { @@ -76,7 +77,7 @@ where S: ServerFn + 'static, S::Output: 'static, { - type Target = ArcAction>>; + type Target = ArcAction>; fn deref(&self) -> &Self::Target { &self.inner @@ -131,7 +132,7 @@ where S: ServerFn + 'static, S::Output: 'static, { - inner: Action>>, + inner: Action>, #[cfg(any(debug_assertions, leptos_debuginfo))] defined_at: &'static Location<'static>, } @@ -146,7 +147,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 +183,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 e33a873bfd..19799ec26b 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(any(debug_assertions, leptos_debuginfo))] 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(any(debug_assertions, leptos_debuginfo))] 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 464341e886..99a205af47 100644 --- a/server_fn/Cargo.toml +++ b/server_fn/Cargo.toml @@ -53,6 +53,7 @@ bytes = "1.9" http-body-util = { version = "0.1.2", optional = true } rkyv = { version = "0.8.9", 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..c67a60207c 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, IntoAppError, 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,10 @@ 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_app_error() + }); // at this point, the future has successfully resolved without being dropped, so we // can prevent the `AbortController` from firing @@ -83,7 +85,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, IntoAppError, ServerFnErrorErr}, + request::reqwest::CLIENT, + }; use futures::TryFutureExt; use reqwest::{Request, Response}; use std::future::Future; @@ -91,17 +96,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 { - CLIENT - .execute(req) - .map_err(|e| ServerFnError::Request(e.to_string())) + ) -> impl Future> + Send { + 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 a5a91c8117..d9952a0a48 100644 --- a/server_fn/src/codec/cbor.rs +++ b/server_fn/src/codec/cbor.rs @@ -1,8 +1,8 @@ use super::{Encoding, FromReq, FromRes, IntoReq, IntoRes}; use crate::{ - error::ServerFnError, + error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::{ClientReq, Req}, - response::{ClientRes, Res}, + response::{ClientRes, TryRes}, }; use bytes::Bytes; use http::Method; @@ -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| { + ServerFnErrorErr::Serialization(e.to_string()).into_app_error() + })?; 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| ServerFnErrorErr::Args(e.to_string()).into_app_error()) } } -impl IntoRes for T +impl IntoRes for T where - Response: Res, + Response: TryRes, 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| { + ServerFnErrorErr::Serialization(e.to_string()).into_app_error() + })?; 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| ServerFnErrorErr::Args(e.to_string()).into_app_error()) } } @@ -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 20872c467d..e67a2d2dee 100644 --- a/server_fn/src/codec/json.rs +++ b/server_fn/src/codec/json.rs @@ -1,8 +1,8 @@ use super::{Encoding, FromReq, FromRes, Streaming}; use crate::{ - error::{NoCustomError, ServerFnError}, + error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::{ClientReq, Req}, - response::{ClientRes, Res}, + response::{ClientRes, TryRes}, IntoReq, IntoRes, }; use bytes::Bytes; @@ -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| { + ServerFnErrorErr::Serialization(e.to_string()).into_app_error() + })?; 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| ServerFnErrorErr::Args(e.to_string()).into_app_error()) } } -impl IntoRes for T +impl IntoRes for T where - Response: Res, + Response: TryRes, 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| { + ServerFnErrorErr::Serialization(e.to_string()).into_app_error() + })?; 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| { + ServerFnErrorErr::Deserialization(e.to_string()).into_app_error() + }) } } @@ -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))) } } -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,61 @@ 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| { + ServerFnErrorErr::Deserialization(e.to_string()) + .into_app_error() + }) }) })); Ok(s.into()) } } -impl IntoRes - for JsonStream +impl IntoRes for JsonStream where - Response: Res, - CustErr: 'static, + Response: TryRes, 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_app_error() + }) }), ) } } -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_app_error() + }) }) }))) } diff --git a/server_fn/src/codec/mod.rs b/server_fn/src/codec/mod.rs index fc5bb37846..6b029f12ee 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_app_error())?; /// // 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_app_error()) /// } /// } /// ``` -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_app_error()) /// } /// } /// ``` -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..339137f84a 100644 --- a/server_fn/src/codec/msgpack.rs +++ b/server_fn/src/codec/msgpack.rs @@ -1,8 +1,8 @@ use super::{Encoding, FromReq, FromRes, IntoReq, IntoRes}; use crate::{ - error::ServerFnError, + error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::{ClientReq, Req}, - response::{ClientRes, Res}, + response::{ClientRes, TryRes}, }; use bytes::Bytes; use http::Method; @@ -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| { + ServerFnErrorErr::Serialization(e.to_string()).into_app_error() + })?; 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_app_error()) } } -impl IntoRes for T +impl IntoRes for T where - Response: Res, + Response: TryRes, 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| { + ServerFnErrorErr::Serialization(e.to_string()).into_app_error() + })?; 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| { + ServerFnErrorErr::Deserialization(e.to_string()).into_app_error() + }) } } diff --git a/server_fn/src/codec/multipart.rs b/server_fn/src/codec/multipart.rs index 998e822260..75e8921b6b 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,20 +71,20 @@ 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()) .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 b78dae63b2..f1f4ede4e8 100644 --- a/server_fn/src/codec/postcard.rs +++ b/server_fn/src/codec/postcard.rs @@ -1,8 +1,8 @@ use super::{Encoding, FromReq, FromRes, IntoReq, IntoRes}; use crate::{ - error::ServerFnError, + error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::{ClientReq, Req}, - response::{ClientRes, Res}, + response::{ClientRes, TryRes}, }; use bytes::Bytes; use http::Method; @@ -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| { + ServerFnErrorErr::Serialization(e.to_string()).into_app_error() + })?; 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| ServerFnErrorErr::Args(e.to_string()).into_app_error()) } } -impl IntoRes for T +impl IntoRes for T where - Response: Res, + Response: TryRes, 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| { + ServerFnErrorErr::Serialization(e.to_string()).into_app_error() + })?; 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| { + 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 9aed0ff83a..8cfebca964 100644 --- a/server_fn/src/codec/rkyv.rs +++ b/server_fn/src/codec/rkyv.rs @@ -1,8 +1,8 @@ use super::{Encoding, FromReq, FromRes, IntoReq, IntoRes}; use crate::{ - error::ServerFnError, + error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::{ClientReq, Req}, - response::{ClientRes, Res}, + response::{ClientRes, TryRes}, }; use bytes::Bytes; use futures::StreamExt; @@ -29,39 +29,38 @@ 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| { + 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) } } -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(e); } Ok(bytes) => { for byte in bytes { @@ -71,36 +70,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_app_error()) } } -impl IntoRes for T +impl IntoRes for T where - Response: Res, + Response: TryRes, 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| { + ServerFnErrorErr::Serialization(format!("{e:?}")).into_app_error() + })?; 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_app_error() + }) } } diff --git a/server_fn/src/codec/serde_lite.rs b/server_fn/src/codec/serde_lite.rs index b71b9390fe..e5bb4e7271 100644 --- a/server_fn/src/codec/serde_lite.rs +++ b/server_fn/src/codec/serde_lite.rs @@ -1,8 +1,8 @@ use super::{Encoding, FromReq, FromRes}; use crate::{ - error::ServerFnError, + error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::{ClientReq, Req}, - response::{ClientRes, Res}, + response::{ClientRes, TryRes}, IntoReq, IntoRes, }; use http::Method; @@ -15,68 +15,68 @@ 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| { + ServerFnErrorErr::Serialization(e.to_string()).into_app_error() + })?) + .map_err(|e| { + ServerFnErrorErr::Serialization(e.to_string()).into_app_error() + })?; 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| ServerFnError::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()) } } -impl IntoRes for T +impl IntoRes for T where - Response: Res, + Response: TryRes, 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| { + ServerFnErrorErr::Serialization(e.to_string()).into_app_error() + })?) + .map_err(|e| { + ServerFnErrorErr::Serialization(e.to_string()).into_app_error() + })?; 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| ServerFnError::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 fd406209a3..f1d5418c74 100644 --- a/server_fn/src/codec/stream.rs +++ b/server_fn/src/codec/stream.rs @@ -1,9 +1,9 @@ use super::{Encoding, FromReq, FromRes, IntoReq}; use crate::{ - error::{NoCustomError, ServerFnError}, + error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::{ClientReq, Req}, - response::{ClientRes, Res}, - IntoRes, + response::{ClientRes, TryRes}, + IntoRes, ServerFnError, }; use bytes::Bytes; use futures::{Stream, StreamExt}; @@ -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: TryRes, + 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,33 @@ 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))) } } -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 +187,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 +204,32 @@ 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_app_error() + }) }) })); Ok(s.into()) } } -impl IntoRes - for TextStream +impl IntoRes for TextStream where - Response: Res, - CustErr: 'static, + Response: TryRes, + 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 +237,19 @@ 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_app_error() + }) }) })))) } diff --git a/server_fn/src/codec/url.rs b/server_fn/src/codec/url.rs index 38e7894649..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::ServerFnError, + error::{FromServerFnError, IntoAppError, ServerFnErrorErr}, request::{ClientReq, Req}, }; use http::Method; @@ -17,32 +17,33 @@ 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| { + ServerFnErrorErr::Serialization(e.to_string()).into_app_error() + })?; 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| { + ServerFnErrorErr::Args(e.to_string()).into_app_error() + })?; Ok(args) } } @@ -52,32 +53,33 @@ 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| { + ServerFnErrorErr::Serialization(e.to_string()).into_app_error() + })?; 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| { + ServerFnErrorErr::Args(e.to_string()).into_app_error() + })?; Ok(args) } } @@ -86,18 +88,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 +112,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 +120,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..432833b5f0 100644 --- a/server_fn/src/error.rs +++ b/server_fn/src/error.rs @@ -1,7 +1,9 @@ -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}, + fmt::{self, Display, Write}, str::FromStr, }; use thiserror::Error; @@ -13,7 +15,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)) } } @@ -35,6 +37,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 +62,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 +92,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 +113,7 @@ impl ViaError } // A type tag for ServerFnError so we can special case it +#[deprecated] pub(crate) trait ServerFnErrorKind {} impl ServerFnErrorKind for ServerFnError {} @@ -131,7 +155,8 @@ impl ViaError for WrapError { } } -/// Type for errors that can occur when using server functions. +/// 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 @@ -142,6 +167,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 +183,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 +231,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) => @@ -214,30 +249,45 @@ where } } -/// 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 like `strum` to derive `FromStr` and `Display` for your -/// custom error type. -/// -/// This is implemented for the default [`ServerFnError`], which uses [`NoCustomError`]. -pub trait ServerFnErrorSerde: Sized { - /// 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 ServerFnErrorSerde for ServerFnError +impl FromServerFnError for ServerFnError where - CustErr: FromStr + Display, + CustErr: std::fmt::Debug + + Display + + Serialize + + DeserializeOwned + + 'static + + FromStr + + Display, { - fn ser(&self) -> Result { + fn from_server_fn_error(value: ServerFnErrorErr) -> Self { + match value { + ServerFnErrorErr::Registration(value) => { + ServerFnError::Registration(value) + } + ServerFnErrorErr::Request(value) => ServerFnError::Request(value), + ServerFnErrorErr::ServerError(value) => { + ServerFnError::ServerError(value) + } + ServerFnErrorErr::MiddlewareError(value) => { + ServerFnError::MiddlewareError(value) + } + ServerFnErrorErr::Deserialization(value) => { + ServerFnError::Deserialization(value) + } + ServerFnErrorErr::Serialization(value) => { + ServerFnError::Serialization(value) + } + ServerFnErrorErr::Args(value) => ServerFnError::Args(value), + ServerFnErrorErr::MissingArg(value) => { + ServerFnError::MissingArg(value) + } + ServerFnErrorErr::Response(value) => ServerFnError::Response(value), + } + } + + fn ser(&self) -> String { let mut buf = String::new(); - match self { + let result = match self { ServerFnError::WrappedServerError(e) => { write!(&mut buf, "WrappedServerFn|{e}") } @@ -249,6 +299,9 @@ where ServerFnError::ServerError(e) => { write!(&mut buf, "ServerError|{e}") } + ServerFnError::MiddlewareError(e) => { + write!(&mut buf, "MiddlewareError|{e}") + } ServerFnError::Deserialization(e) => { write!(&mut buf, "Deserialization|{e}") } @@ -259,8 +312,11 @@ where ServerFnError::MissingArg(e) => { write!(&mut buf, "MissingArg|{e}") } - }?; - Ok(buf) + }; + match result { + Ok(()) => buf, + Err(_) => "Serialization|".to_string(), + } } fn de(data: &str) -> Self { @@ -311,20 +367,13 @@ where } } -/// Type for errors that can occur when using server functions. -/// -/// 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)] -pub enum ServerFnErrorErr { - /// A user-defined custom error type, which defaults to [`NoCustomError`]. - #[error("internal error: {0}")] - WrappedServerError(E), +/// 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", + 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), @@ -334,6 +383,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), @@ -351,34 +403,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::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. /// @@ -386,15 +410,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 +426,7 @@ impl ServerFnUrlError { } /// The error itself. - pub fn error(&self) -> &ServerFnError { + pub fn error(&self) -> &E { &self.error } @@ -412,17 +436,11 @@ 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(), - ); + .append_pair("__err", &URL_SAFE.encode(self.error.ser())); Ok(url) } @@ -448,16 +466,102 @@ 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_app_error(); + } + }; + let s = match String::from_utf8(decoded) { + Ok(s) => s, + Err(err) => { + return ServerFnErrorErr::Deserialization(err.to_string()) + .into_app_error(); + } + }; + 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.ser()) } } + +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: + 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`]. +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] +fn assert_from_server_fn_error_impl() { + fn assert_impl() {} + + assert_impl::(); +} diff --git a/server_fn/src/lib.rs b/server_fn/src/lib.rs index 5b656d33a1..1c3132ee18 100644 --- a/server_fn/src/lib.rs +++ b/server_fn/src/lib.rs @@ -132,15 +132,15 @@ use codec::{Encoding, FromReq, FromRes, IntoReq, IntoRes}; pub use const_format; use dashmap::DashMap; pub use error::ServerFnError; -use error::ServerFnErrorSerde; #[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)] @@ -148,7 +148,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; @@ -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. /// @@ -222,9 +222,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 +239,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( @@ -265,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), ) }); @@ -298,8 +300,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 +314,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 +325,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 +345,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?; @@ -387,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(), } } @@ -424,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 @@ -431,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 }) } @@ -444,6 +456,7 @@ impl Clone for ServerFnTraitObj { method: self.method.clone(), handler: self.handler, middleware: self.middleware, + ser: self.ser, } } } @@ -467,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}; @@ -490,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))), ); } @@ -539,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); } @@ -578,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))), ); } @@ -603,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 @@ -644,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 0789bf719a..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,37 +43,36 @@ 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::{response::Res, ServerFnError}; + use crate::{error::ServerFnErrorErr, response::Res, ServerFnError}; 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: 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 = ServerFnError::new(e); - Response::::error_response(&path, &err) + let err = + ser(ServerFnErrorErr::MiddlewareError(e.to_string())); + Response::::error_response(&path, err) }) }) } @@ -80,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) }) } } @@ -97,7 +116,7 @@ mod axum { &self, inner: BoxedService, Response>, ) -> BoxedService, Response> { - BoxedService(Box::new(self.layer(inner))) + BoxedService::new(inner.ser, self.layer(inner)) } } } @@ -105,33 +124,31 @@ mod axum { #[cfg(feature = "actix")] mod actix { use crate::{ + error::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: 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 = ServerFnError::new(e); - ActixResponse::error_response(&path, &err).take() + let err = + ser(ServerFnErrorErr::MiddlewareError(e.to_string())); + ActixResponse::error_response(&path, err).take() }) }) } @@ -141,18 +158,20 @@ mod actix { where S: actix_web::dev::Service, S::Future: Send + 'static, - S::Error: Into + Debug + Display + 'static, + 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 = ServerFnError::new(e); - 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/request/actix.rs b/server_fn/src/request/actix.rs index 9235eacfe6..3bc63bd12f 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, IntoAppError, ServerFnErrorErr}, + request::Req, +}; use actix_web::{web::Payload, HttpRequest}; use bytes::Bytes; use futures::{Stream, StreamExt}; @@ -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,39 @@ 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_app_error() + }) }) } - 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_server_fn_error(ServerFnErrorErr::Deserialization( + e.to_string(), + )) + })?; + String::from_utf8(bytes.into()).map_err(|e| { + E::from_server_fn_error(ServerFnErrorErr::Deserialization( + e.to_string(), + )) + }) }) } fn try_into_stream( self, - ) -> Result< - impl Stream> + Send, - ServerFnError, - > { + ) -> Result> + Send, E> { let payload = self.0.take().1; let stream = payload.map(|res| { res.map_err(|e| ServerFnError::Deserialization(e.to_string())) diff --git a/server_fn/src/request/axum.rs b/server_fn/src/request/axum.rs index e26f7c7676..da02a535eb 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, IntoAppError, 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,29 @@ 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_app_error() + }) } - 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_app_error() + }) } 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_app_error() + }) })) } } diff --git a/server_fn/src/request/browser.rs b/server_fn/src/request/browser.rs index 550b898cdf..839c4d8fe7 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,11 @@ 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_server_fn_error(ServerFnErrorErr::Request( + e.to_string(), + )) + })?, abort_ctrl, }))) } @@ -117,7 +127,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 +139,11 @@ 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_server_fn_error(ServerFnErrorErr::Request( + e.to_string(), + )) + })?, abort_ctrl, }))) } @@ -139,7 +153,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 +167,11 @@ 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_server_fn_error(ServerFnErrorErr::Request( + e.to_string(), + )) + })?, abort_ctrl, }))) } @@ -162,7 +180,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 +191,11 @@ 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_server_fn_error(ServerFnErrorErr::Request( + e.to_string(), + )) + })?, abort_ctrl, }))) } @@ -183,17 +205,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_server_fn_error(ServerFnErrorErr::Serialization( + e.as_string().unwrap_or_else(|| { "Could not serialize FormData to URLSearchParams" .to_string() - }, + }), )) })?; Ok(Self(SendWrapper::new(RequestInner { @@ -202,7 +224,11 @@ 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_server_fn_error(ServerFnErrorErr::Request( + e.to_string(), + )) + })?, abort_ctrl, }))) } @@ -212,11 +238,16 @@ 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_server_fn_error(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..99a2838577 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, IntoAppError, 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_app_error() }) } 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..e1ade8d76b 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, IntoAppError, 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,22 @@ 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()))?; + 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| ServerFnError::Request(e.to_string()))?; + .map_err(|e| { + E::from_server_fn_error(ServerFnErrorErr::Request( + e.to_string(), + )) + })?; Ok(req) } @@ -35,7 +46,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 +54,9 @@ 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_app_error() + }) } fn try_new_post_bytes( @@ -51,7 +64,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 +72,24 @@ 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_app_error() + }) } 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_app_error() + }) } fn try_new_post_form_data( @@ -80,14 +97,16 @@ 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_app_error() + }) } fn try_new_streaming( @@ -95,7 +114,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 +131,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..3a168b3dad 100644 --- a/server_fn/src/response/actix.rs +++ b/server_fn/src/response/actix.rs @@ -1,6 +1,6 @@ -use super::Res; +use super::{Res, TryRes}; 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 TryRes 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,23 +58,23 @@ 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 { +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().unwrap_or_else(|_| err.to_string())), + .body(err), )) } diff --git a/server_fn/src/response/browser.rs b/server_fn/src/response/browser.rs index 6c4cfcc1b5..8f16f03de9 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, IntoAppError, ServerFnErrorErr}, + redirect::REDIRECT_HEADER, +}; use bytes::Bytes; use futures::{Stream, StreamExt}; pub use gloo_net::http::Response; @@ -12,48 +15,39 @@ 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_app_error() + }) }) } - 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_app_error() + }) }) } 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_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 f9e10b5f4c..cdb06093fb 100644 --- a/server_fn/src/response/generic.rs +++ b/server_fn/src/response/generic.rs @@ -12,18 +12,15 @@ //! * `wasm32-wasip*` integration crate `leptos_wasi` is using this //! crate under the hood. -use super::Res; +use super::{Res, TryRes}; use crate::error::{ - ServerFnError, ServerFnErrorErr, ServerFnErrorSerde, SERVER_FN_ERROR_HEADER, + FromServerFnError, IntoAppError, 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,55 +41,55 @@ impl From for Body { } } -impl Res for Response +impl TryRes 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_app_error() + }) } - 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_app_error() + }) } 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_app_error() + }) } +} - fn error_response(path: &str, err: &ServerFnError) -> 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().unwrap_or_else(|_| err.to_string()).into()) + .body(err.into()) .unwrap() } diff --git a/server_fn/src/response/http.rs b/server_fn/src/response/http.rs index e8117f75cc..15caa5b95d 100644 --- a/server_fn/src/response/http.rs +++ b/server_fn/src/response/http.rs @@ -1,65 +1,61 @@ -use super::Res; +use super::{Res, TryRes}; use crate::error::{ - ServerFnError, ServerFnErrorErr, ServerFnErrorSerde, SERVER_FN_ERROR_HEADER, + FromServerFnError, IntoAppError, 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 TryRes 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_app_error() + }) } - 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_app_error() + }) } 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| { + ServerFnErrorErr::Response(e.to_string()).into_app_error() + }) } +} - fn error_response(path: &str, err: &ServerFnError) -> 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().unwrap_or_else(|_| err.to_string()).into()) + .body(err.into()) .unwrap() } diff --git a/server_fn/src/response/mod.rs b/server_fn/src/response/mod.rs index 6a0f60bace..f479dbaa81 100644 --- a/server_fn/src/response/mod.rs +++ b/server_fn/src/response/mod.rs @@ -13,62 +13,49 @@ 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 TryRes 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; +} +/// 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: &ServerFnError) -> 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); } /// 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 +78,25 @@ 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 TryRes 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 try_from_stream( + _content_type: &str, + _data: impl Stream>, + ) -> Result { unreachable!() } +} - fn try_from_stream( - _content_type: &str, - _data: impl Stream>>, - ) -> Result> { +impl Res for BrowserMockRes { + fn error_response(_path: &str, _err: String) -> Self { unreachable!() } diff --git a/server_fn/src/response/reqwest.rs b/server_fn/src/response/reqwest.rs index f60338e48d..7bbff5e8cc 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, IntoAppError, 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_app_error() + }) } - 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_app_error() + }) } fn try_into_stream( self, - ) -> Result< - impl Stream> + Send + 'static, - ServerFnError, - > { - Ok(self - .bytes_stream() - .map_err(|e| ServerFnError::Response(e.to_string()))) + ) -> Result> + Send + 'static, E> { + Ok(self.bytes_stream().map_err(|e| { + ServerFnErrorErr::Response(e.to_string()).into_app_error() + })) } fn status(&self) -> u16 { diff --git a/server_fn_macro/src/lib.rs b/server_fn_macro/src/lib.rs index 864cba1204..c955418b68 100644 --- a/server_fn_macro/src/lib.rs +++ b/server_fn_macro/src/lib.rs @@ -382,13 +382,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)), ) }} } @@ -730,12 +725,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) = @@ -746,25 +741,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)); } } } @@ -772,8 +750,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", )) }