feat: enhancing server_fn errors (#3811)

* feat: enhancing server_fn errors

* fix: `server_fn` `not_result.stderr` line numbers

* fix: example server_fns_axum

* fix: not need to force ser/de traits

* fix(docs): deserialize error comment

* fix: remove unneeded traits bounds
This commit is contained in:
Saber Haj Rabiee
2025-04-10 14:04:16 -07:00
committed by GitHub
parent ba7bfd8bac
commit 7637e586d5
33 changed files with 561 additions and 347 deletions

View File

@@ -41,10 +41,8 @@ pub trait Client<Error, InputStreamError = Error, OutputStreamError = Error> {
) -> impl Future<
Output = Result<
(
impl Stream<Item = Result<Bytes, OutputStreamError>>
+ Send
+ 'static,
impl Sink<Result<Bytes, InputStreamError>> + Send + 'static,
impl Stream<Item = Result<Bytes, Bytes>> + Send + 'static,
impl Sink<Result<Bytes, Bytes>> + Send + 'static,
),
Error,
>,
@@ -114,12 +112,10 @@ pub mod browser {
) -> impl Future<
Output = Result<
(
impl futures::Stream<Item = Result<Bytes, OutputStreamError>>
+ Send
+ 'static,
impl futures::Sink<Result<Bytes, InputStreamError>>
impl futures::Stream<Item = Result<Bytes, Bytes>>
+ Send
+ 'static,
impl futures::Sink<Result<Bytes, Bytes>> + Send + 'static,
),
Error,
>,
@@ -141,6 +137,7 @@ pub mod browser {
OutputStreamError::from_server_fn_error(
ServerFnErrorErr::Request(err.to_string()),
)
.ser()
})
.map_ok(move |msg| match msg {
Message::Text(text) => Bytes::from(text),
@@ -198,26 +195,26 @@ pub mod browser {
}
}
let sink = sink.with(
|message: Result<Bytes, InputStreamError>| async move {
let sink =
sink.with(|message: Result<Bytes, Bytes>| async move {
match message {
Ok(message) => Ok(Message::Bytes(message.into())),
Err(err) => {
let err = InputStreamError::de(err);
web_sys::console::error_1(
&js_sys::JsString::from(err.ser()),
&js_sys::JsString::from(err.to_string()),
);
const CLOSE_CODE_ERROR: u16 = 1011;
Err(WebSocketError::ConnectionClose(
CloseEvent {
code: CLOSE_CODE_ERROR,
reason: err.ser(),
reason: err.to_string(),
was_clean: true,
},
))
}
}
},
);
});
let sink = SendWrapperSink::new(Box::pin(sink));
Ok((stream, sink))
@@ -262,10 +259,10 @@ pub mod reqwest {
path: &str,
) -> Result<
(
impl futures::Stream<Item = Result<bytes::Bytes, E>>
impl futures::Stream<Item = Result<bytes::Bytes, Bytes>>
+ Send
+ 'static,
impl futures::Sink<Result<bytes::Bytes, E>> + Send + 'static,
impl futures::Sink<Result<bytes::Bytes, Bytes>> + Send + 'static,
),
E,
> {
@@ -293,18 +290,20 @@ pub mod reqwest {
Ok(msg) => Ok(msg.into_data()),
Err(e) => Err(E::from_server_fn_error(
ServerFnErrorErr::Request(e.to_string()),
)),
)
.ser()),
}),
write.with(|msg: Result<Bytes, E>| async move {
write.with(|msg: Result<Bytes, Bytes>| async move {
match msg {
Ok(msg) => {
Ok(tokio_tungstenite::tungstenite::Message::Binary(
msg,
))
}
Err(e) => {
Err(err) => {
let err = E::de(err);
Err(tokio_tungstenite::tungstenite::Error::Io(
std::io::Error::other(e.ser()),
std::io::Error::other(err.to_string()),
))
}
}

View File

@@ -1,5 +1,5 @@
use super::{Patch, Post, Put};
use crate::{ContentType, Decodes, Encodes};
use crate::{ContentType, Decodes, Encodes, Format, FormatType};
use bytes::Bytes;
use serde::{de::DeserializeOwned, Serialize};
@@ -10,15 +10,19 @@ impl ContentType for CborEncoding {
const CONTENT_TYPE: &'static str = "application/cbor";
}
impl FormatType for CborEncoding {
const FORMAT_TYPE: Format = Format::Binary;
}
impl<T> Encodes<T> for CborEncoding
where
T: Serialize,
{
type Error = ciborium::ser::Error<std::io::Error>;
fn encode(value: T) -> Result<Bytes, Self::Error> {
fn encode(value: &T) -> Result<Bytes, Self::Error> {
let mut buffer: Vec<u8> = Vec::new();
ciborium::ser::into_writer(&value, &mut buffer)?;
ciborium::ser::into_writer(value, &mut buffer)?;
Ok(Bytes::from(buffer))
}
}

View File

@@ -1,5 +1,5 @@
use super::{Patch, Post, Put};
use crate::{ContentType, Decodes, Encodes};
use crate::{ContentType, Decodes, Encodes, Format, FormatType};
use bytes::Bytes;
use serde::{de::DeserializeOwned, Serialize};
@@ -10,14 +10,18 @@ impl ContentType for JsonEncoding {
const CONTENT_TYPE: &'static str = "application/json";
}
impl FormatType for JsonEncoding {
const FORMAT_TYPE: Format = Format::Text;
}
impl<T> Encodes<T> for JsonEncoding
where
T: Serialize,
{
type Error = serde_json::Error;
fn encode(output: T) -> Result<Bytes, Self::Error> {
serde_json::to_vec(&output).map(Bytes::from)
fn encode(output: &T) -> Result<Bytes, Self::Error> {
serde_json::to_vec(output).map(Bytes::from)
}
}

View File

@@ -1,6 +1,6 @@
use crate::{
codec::{Patch, Post, Put},
ContentType, Decodes, Encodes,
ContentType, Decodes, Encodes, Format, FormatType,
};
use bytes::Bytes;
use serde::{de::DeserializeOwned, Serialize};
@@ -12,14 +12,18 @@ impl ContentType for MsgPackEncoding {
const CONTENT_TYPE: &'static str = "application/msgpack";
}
impl FormatType for MsgPackEncoding {
const FORMAT_TYPE: Format = Format::Binary;
}
impl<T> Encodes<T> for MsgPackEncoding
where
T: Serialize,
{
type Error = rmp_serde::encode::Error;
fn encode(value: T) -> Result<Bytes, Self::Error> {
rmp_serde::to_vec(&value).map(Bytes::from)
fn encode(value: &T) -> Result<Bytes, Self::Error> {
rmp_serde::to_vec(value).map(Bytes::from)
}
}

View File

@@ -1,6 +1,6 @@
use super::{Encoding, FromReq};
use crate::{
error::FromServerFnError,
error::{FromServerFnError, ServerFnErrorWrapper},
request::{browser::BrowserFormData, ClientReq, Req},
ContentType, IntoReq,
};
@@ -59,7 +59,8 @@ impl From<FormData> for MultipartData {
}
}
impl<E, T, Request> IntoReq<MultipartFormData, Request, E> for T
impl<E: FromServerFnError, T, Request> IntoReq<MultipartFormData, Request, E>
for T
where
Request: ClientReq<E, FormData = BrowserFormData>,
T: Into<MultipartData>,
@@ -78,7 +79,7 @@ impl<E, T, Request> FromReq<MultipartFormData, Request, E> for T
where
Request: Req<E> + Send + 'static,
T: From<MultipartData>,
E: FromServerFnError,
E: FromServerFnError + Send + Sync,
{
async fn from_req(req: Request) -> Result<Self, E> {
let boundary = req
@@ -87,7 +88,7 @@ where
.expect("couldn't parse boundary");
let stream = req.try_into_stream()?;
let data = multer::Multipart::new(
stream.map(|data| data.map_err(|e| e.ser())),
stream.map(|data| data.map_err(|e| ServerFnErrorWrapper(E::de(e)))),
boundary,
);
Ok(MultipartData::Server(data).into())

View File

@@ -25,7 +25,7 @@ where
E: FromServerFnError,
{
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
let data = Encoding::encode(self).map_err(|e| {
let data = Encoding::encode(&self).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Request::try_new_patch_bytes(
@@ -60,7 +60,7 @@ where
T: Send,
{
async fn into_res(self) -> Result<Response, E> {
let data = Encoding::encode(self).map_err(|e| {
let data = Encoding::encode(&self).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Response::try_from_bytes(Encoding::CONTENT_TYPE, data)

View File

@@ -25,7 +25,7 @@ where
E: FromServerFnError,
{
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
let data = Encoding::encode(self).map_err(|e| {
let data = Encoding::encode(&self).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Request::try_new_post_bytes(path, accepts, Encoding::CONTENT_TYPE, data)
@@ -55,7 +55,7 @@ where
T: Send,
{
async fn into_res(self) -> Result<Response, E> {
let data = Encoding::encode(self).map_err(|e| {
let data = Encoding::encode(&self).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Response::try_from_bytes(Encoding::CONTENT_TYPE, data)

View File

@@ -1,6 +1,6 @@
use crate::{
codec::{Patch, Post, Put},
ContentType, Decodes, Encodes,
ContentType, Decodes, Encodes, Format, FormatType,
};
use bytes::Bytes;
use serde::{de::DeserializeOwned, Serialize};
@@ -12,14 +12,18 @@ impl ContentType for PostcardEncoding {
const CONTENT_TYPE: &'static str = "application/x-postcard";
}
impl FormatType for PostcardEncoding {
const FORMAT_TYPE: Format = Format::Binary;
}
impl<T> Encodes<T> for PostcardEncoding
where
T: Serialize,
{
type Error = postcard::Error;
fn encode(value: T) -> Result<Bytes, Self::Error> {
postcard::to_allocvec(&value).map(Bytes::from)
fn encode(value: &T) -> Result<Bytes, Self::Error> {
postcard::to_allocvec(value).map(Bytes::from)
}
}

View File

@@ -25,7 +25,7 @@ where
E: FromServerFnError,
{
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
let data = Encoding::encode(self).map_err(|e| {
let data = Encoding::encode(&self).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Request::try_new_put_bytes(path, accepts, Encoding::CONTENT_TYPE, data)
@@ -55,7 +55,7 @@ where
T: Send,
{
async fn into_res(self) -> Result<Response, E> {
let data = Encoding::encode(self).map_err(|e| {
let data = Encoding::encode(&self).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Response::try_from_bytes(Encoding::CONTENT_TYPE, data)

View File

@@ -1,6 +1,6 @@
use crate::{
codec::{Patch, Post, Put},
ContentType, Decodes, Encodes,
ContentType, Decodes, Encodes, Format, FormatType,
};
use bytes::Bytes;
use rkyv::{
@@ -24,6 +24,10 @@ impl ContentType for RkyvEncoding {
const CONTENT_TYPE: &'static str = "application/rkyv";
}
impl FormatType for RkyvEncoding {
const FORMAT_TYPE: Format = Format::Binary;
}
impl<T> Encodes<T> for RkyvEncoding
where
T: Archive + for<'a> Serialize<RkyvSerializer<'a>>,
@@ -32,8 +36,8 @@ where
{
type Error = rancor::Error;
fn encode(value: T) -> Result<Bytes, Self::Error> {
let encoded = rkyv::to_bytes::<rancor::Error>(&value)?;
fn encode(value: &T) -> Result<Bytes, Self::Error> {
let encoded = rkyv::to_bytes::<rancor::Error>(value)?;
Ok(Bytes::copy_from_slice(encoded.as_ref()))
}
}

View File

@@ -1,7 +1,7 @@
use crate::{
codec::{Patch, Post, Put},
error::ServerFnErrorErr,
ContentType, Decodes, Encodes,
ContentType, Decodes, Encodes, Format, FormatType,
};
use bytes::Bytes;
use serde_lite::{Deserialize, Serialize};
@@ -13,13 +13,17 @@ impl ContentType for SerdeLiteEncoding {
const CONTENT_TYPE: &'static str = "application/json";
}
impl FormatType for SerdeLiteEncoding {
const FORMAT_TYPE: Format = Format::Text;
}
impl<T> Encodes<T> for SerdeLiteEncoding
where
T: Serialize,
{
type Error = ServerFnErrorErr;
fn encode(value: T) -> Result<Bytes, Self::Error> {
fn encode(value: &T) -> Result<Bytes, Self::Error> {
serde_json::to_vec(
&value
.serialize()

View File

@@ -1,6 +1,6 @@
use super::{Encoding, FromReq, FromRes, IntoReq};
use crate::{
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
error::{FromServerFnError, ServerFnErrorErr},
request::{ClientReq, Req},
response::{ClientRes, TryRes},
ContentType, IntoRes, ServerFnError,
@@ -50,7 +50,7 @@ where
impl<E, T, Request> FromReq<Streaming, Request, E> for T
where
Request: Req<E> + Send + 'static,
T: From<ByteStream<E>> + 'static,
T: From<ByteStream> + 'static,
{
async fn from_req(req: Request) -> Result<Self, E> {
let data = req.try_into_stream()?;
@@ -71,34 +71,37 @@ where
/// end before the output will begin.
///
/// Streaming requests are only allowed over HTTP2 or HTTP3.
pub struct ByteStream<E>(Pin<Box<dyn Stream<Item = Result<Bytes, E>> + Send>>);
pub struct ByteStream(Pin<Box<dyn Stream<Item = Result<Bytes, Bytes>> + Send>>);
impl<E> ByteStream<E> {
impl ByteStream {
/// Consumes the wrapper, returning a stream of bytes.
pub fn into_inner(self) -> impl Stream<Item = Result<Bytes, E>> + Send {
pub fn into_inner(self) -> impl Stream<Item = Result<Bytes, Bytes>> + Send {
self.0
}
}
impl<E> Debug for ByteStream<E> {
impl Debug for ByteStream {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("ByteStream").finish()
}
}
impl<E> ByteStream<E> {
impl ByteStream {
/// Creates a new `ByteStream` from the given stream.
pub fn new<T>(
pub fn new<T, E>(
value: impl Stream<Item = Result<T, E>> + Send + 'static,
) -> Self
where
T: Into<Bytes>,
E: Into<Bytes>,
{
Self(Box::pin(value.map(|value| value.map(Into::into))))
Self(Box::pin(
value.map(|value| value.map(Into::into).map_err(Into::into)),
))
}
}
impl<E, S, T> From<S> for ByteStream<E>
impl<S, T> From<S> for ByteStream
where
S: Stream<Item = T> + Send + 'static,
T: Into<Bytes>,
@@ -108,7 +111,7 @@ where
}
}
impl<E, Response> IntoRes<Streaming, Response, E> for ByteStream<E>
impl<E, Response> IntoRes<Streaming, Response, E> for ByteStream
where
Response: TryRes<E>,
E: 'static,
@@ -118,7 +121,7 @@ where
}
}
impl<E, Response> FromRes<Streaming, Response, E> for ByteStream<E>
impl<E, Response> FromRes<Streaming, Response, E> for ByteStream
where
Response: ClientRes<E> + Send,
{
@@ -166,13 +169,13 @@ pub struct TextStream<E = ServerFnError>(
Pin<Box<dyn Stream<Item = Result<String, E>> + Send>>,
);
impl<E> Debug for TextStream<E> {
impl<E: FromServerFnError> Debug for TextStream<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("TextStream").finish()
}
}
impl<E> TextStream<E> {
impl<E: FromServerFnError> TextStream<E> {
/// Creates a new `ByteStream` from the given stream.
pub fn new(
value: impl Stream<Item = Result<String, E>> + Send + 'static,
@@ -181,7 +184,7 @@ impl<E> TextStream<E> {
}
}
impl<E> TextStream<E> {
impl<E: FromServerFnError> TextStream<E> {
/// Consumes the wrapper, returning a stream of text.
pub fn into_inner(self) -> impl Stream<Item = Result<String, E>> + Send {
self.0
@@ -192,6 +195,7 @@ impl<E, S, T> From<S> for TextStream<E>
where
S: Stream<Item = T> + Send + 'static,
T: Into<String>,
E: FromServerFnError,
{
fn from(value: S) -> Self {
Self(Box::pin(value.map(|data| Ok(data.into()))))
@@ -202,7 +206,7 @@ impl<E, T, Request> IntoReq<StreamingText, Request, E> for T
where
Request: ClientReq<E>,
T: Into<TextStream<E>>,
E: 'static,
E: FromServerFnError,
{
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
let data = self.into();
@@ -223,13 +227,16 @@ where
{
async fn from_req(req: Request) -> Result<Self, E> {
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| {
ServerFnErrorErr::Deserialization(e.to_string())
.into_app_error()
})
})
let s = TextStream::new(data.map(|chunk| match chunk {
Ok(bytes) => {
let de = String::from_utf8(bytes.to_vec()).map_err(|e| {
E::from_server_fn_error(ServerFnErrorErr::Deserialization(
e.to_string(),
))
})?;
Ok(de)
}
Err(bytes) => Err(E::de(bytes)),
}));
Ok(s.into())
}
@@ -238,12 +245,13 @@ where
impl<E, Response> IntoRes<StreamingText, Response, E> for TextStream<E>
where
Response: TryRes<E>,
E: 'static,
E: FromServerFnError,
{
async fn into_res(self) -> Result<Response, E> {
Response::try_from_stream(
Streaming::CONTENT_TYPE,
self.into_inner().map(|stream| stream.map(Into::into)),
self.into_inner()
.map(|stream| stream.map(Into::into).map_err(|e| e.ser())),
)
}
}
@@ -255,13 +263,16 @@ where
{
async fn from_res(res: Response) -> Result<Self, E> {
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| {
ServerFnErrorErr::Deserialization(e.to_string())
.into_app_error()
})
})
Ok(TextStream(Box::pin(stream.map(|chunk| match chunk {
Ok(bytes) => {
let de = String::from_utf8(bytes.into()).map_err(|e| {
E::from_server_fn_error(ServerFnErrorErr::Deserialization(
e.to_string(),
))
})?;
Ok(de)
}
Err(bytes) => Err(E::de(bytes)),
}))))
}
}

View File

@@ -1,12 +1,13 @@
#![allow(deprecated)]
use crate::{ContentType, Decodes, Encodes, Format, FormatType};
use base64::{engine::general_purpose::URL_SAFE, Engine as _};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use bytes::Bytes;
use serde::{Deserialize, Serialize};
use std::{
fmt::{self, Display, Write},
str::FromStr,
};
use thiserror::Error;
use throw_error::Error;
use url::Url;
@@ -249,16 +250,109 @@ where
}
}
/// Serializes and deserializes JSON with [`serde_json`].
pub struct ServerFnErrorEncoding;
impl ContentType for ServerFnErrorEncoding {
const CONTENT_TYPE: &'static str = "text/plain";
}
impl FormatType for ServerFnErrorEncoding {
const FORMAT_TYPE: Format = Format::Text;
}
impl<CustErr> Encodes<ServerFnError<CustErr>> for ServerFnErrorEncoding
where
CustErr: Display,
{
type Error = std::fmt::Error;
fn encode(output: &ServerFnError<CustErr>) -> Result<Bytes, Self::Error> {
let mut buf = String::new();
let result = match output {
ServerFnError::WrappedServerError(e) => {
write!(&mut buf, "WrappedServerFn|{e}")
}
ServerFnError::Registration(e) => {
write!(&mut buf, "Registration|{e}")
}
ServerFnError::Request(e) => write!(&mut buf, "Request|{e}"),
ServerFnError::Response(e) => write!(&mut buf, "Response|{e}"),
ServerFnError::ServerError(e) => {
write!(&mut buf, "ServerError|{e}")
}
ServerFnError::MiddlewareError(e) => {
write!(&mut buf, "MiddlewareError|{e}")
}
ServerFnError::Deserialization(e) => {
write!(&mut buf, "Deserialization|{e}")
}
ServerFnError::Serialization(e) => {
write!(&mut buf, "Serialization|{e}")
}
ServerFnError::Args(e) => write!(&mut buf, "Args|{e}"),
ServerFnError::MissingArg(e) => {
write!(&mut buf, "MissingArg|{e}")
}
};
match result {
Ok(()) => Ok(Bytes::from(buf)),
Err(e) => Err(e),
}
}
}
impl<CustErr> Decodes<ServerFnError<CustErr>> for ServerFnErrorEncoding
where
CustErr: FromStr,
{
type Error = String;
fn decode(bytes: Bytes) -> Result<ServerFnError<CustErr>, Self::Error> {
let data = String::from_utf8(bytes.to_vec())
.map_err(|err| format!("UTF-8 conversion error: {}", err))?;
data.split_once('|')
.ok_or_else(|| {
format!("Invalid format: missing delimiter in {data:?}")
})
.and_then(|(ty, data)| match ty {
"WrappedServerFn" => CustErr::from_str(data)
.map(ServerFnError::WrappedServerError)
.map_err(|_| {
format!("Failed to parse CustErr from {data:?}")
}),
"Registration" => {
Ok(ServerFnError::Registration(data.to_string()))
}
"Request" => Ok(ServerFnError::Request(data.to_string())),
"Response" => Ok(ServerFnError::Response(data.to_string())),
"ServerError" => {
Ok(ServerFnError::ServerError(data.to_string()))
}
"MiddlewareError" => {
Ok(ServerFnError::MiddlewareError(data.to_string()))
}
"Deserialization" => {
Ok(ServerFnError::Deserialization(data.to_string()))
}
"Serialization" => {
Ok(ServerFnError::Serialization(data.to_string()))
}
"Args" => Ok(ServerFnError::Args(data.to_string())),
"MissingArg" => Ok(ServerFnError::MissingArg(data.to_string())),
_ => Err(format!("Unknown error type: {ty}")),
})
}
}
impl<CustErr> FromServerFnError for ServerFnError<CustErr>
where
CustErr: std::fmt::Debug
+ Display
+ Serialize
+ DeserializeOwned
+ 'static
+ FromStr
+ Display,
CustErr: std::fmt::Debug + Display + FromStr + 'static,
{
type Encoder = ServerFnErrorEncoding;
fn from_server_fn_error(value: ServerFnErrorErr) -> Self {
match value {
ServerFnErrorErr::Registration(value) => {
@@ -287,74 +381,6 @@ where
}
}
}
fn ser(&self) -> String {
let mut buf = String::new();
let result = match self {
ServerFnError::WrappedServerError(e) => {
write!(&mut buf, "WrappedServerFn|{e}")
}
ServerFnError::Registration(e) => {
write!(&mut buf, "Registration|{e}")
}
ServerFnError::Request(e) => write!(&mut buf, "Request|{e}"),
ServerFnError::Response(e) => write!(&mut buf, "Response|{e}"),
ServerFnError::ServerError(e) => {
write!(&mut buf, "ServerError|{e}")
}
ServerFnError::MiddlewareError(e) => {
write!(&mut buf, "MiddlewareError|{e}")
}
ServerFnError::Deserialization(e) => {
write!(&mut buf, "Deserialization|{e}")
}
ServerFnError::Serialization(e) => {
write!(&mut buf, "Serialization|{e}")
}
ServerFnError::Args(e) => write!(&mut buf, "Args|{e}"),
ServerFnError::MissingArg(e) => {
write!(&mut buf, "MissingArg|{e}")
}
};
match result {
Ok(()) => buf,
Err(_) => "Serialization|".to_string(),
}
}
fn de(data: &str) -> Self {
data.split_once('|')
.and_then(|(ty, data)| match ty {
"WrappedServerFn" => match CustErr::from_str(data) {
Ok(d) => Some(ServerFnError::WrappedServerError(d)),
Err(_) => None,
},
"Registration" => {
Some(ServerFnError::Registration(data.to_string()))
}
"Request" => Some(ServerFnError::Request(data.to_string())),
"Response" => Some(ServerFnError::Response(data.to_string())),
"ServerError" => {
Some(ServerFnError::ServerError(data.to_string()))
}
"Deserialization" => {
Some(ServerFnError::Deserialization(data.to_string()))
}
"Serialization" => {
Some(ServerFnError::Serialization(data.to_string()))
}
"Args" => Some(ServerFnError::Args(data.to_string())),
"MissingArg" => {
Some(ServerFnError::MissingArg(data.to_string()))
}
_ => None,
})
.unwrap_or_else(|| {
ServerFnError::Deserialization(format!(
"Could not deserialize error {data:?}"
))
})
}
}
impl<E> std::error::Error for ServerFnError<E>
@@ -371,7 +397,9 @@ where
}
/// Type for errors that can occur when using server functions. If you need to return a custom error type from a server function, implement `FromServerFnError` for your custom error type.
#[derive(Error, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(
thiserror::Error, Debug, Clone, PartialEq, Eq, Serialize, Deserialize,
)]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
@@ -482,14 +510,7 @@ impl<E: FromServerFnError> ServerFnUrlError<E> {
.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)
E::de(decoded.into())
}
}
@@ -505,34 +526,54 @@ impl<E> From<ServerFnUrlError<ServerFnError<E>>> for ServerFnError<E> {
}
}
#[derive(Debug)]
#[derive(Debug, thiserror::Error)]
#[doc(hidden)]
/// Only used instantly only when a framework needs E: Error.
pub struct ServerFnErrorWrapper<E: FromServerFnError>(pub E);
impl<E: FromServerFnError> Display for ServerFnErrorWrapper<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.ser())
write!(
f,
"{}",
<E::Encoder as FormatType>::into_encoded_string(self.0.ser())
)
}
}
impl<E: FromServerFnError> std::error::Error for ServerFnErrorWrapper<E> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
impl<E: FromServerFnError> FromStr for ServerFnErrorWrapper<E> {
type Err = base64::DecodeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes =
<E::Encoder as FormatType>::from_encoded_string(s).map_err(|e| {
E::from_server_fn_error(ServerFnErrorErr::Deserialization(
e.to_string(),
))
});
let bytes = match bytes {
Ok(bytes) => bytes,
Err(err) => return Ok(Self(err)),
};
let err = E::de(bytes);
Ok(Self(err))
}
}
/// A trait for types that can be returned from a server function.
pub trait FromServerFnError:
std::fmt::Debug + Serialize + DeserializeOwned + 'static
std::fmt::Debug + Sized + Display + 'static
{
/// The encoding strategy used to serialize and deserialize this error type. Must implement the [`Encodes`](server_fn::Encodes) trait for references to the error type.
type Encoder: Encodes<Self> + Decodes<Self>;
/// 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(
/// Converts the custom error type to a [`String`].
fn ser(&self) -> Bytes {
Self::Encoder::encode(self).unwrap_or_else(|e| {
Self::Encoder::encode(&Self::from_server_fn_error(
ServerFnErrorErr::Serialization(e.to_string()),
))
.expect(
@@ -542,9 +583,9 @@ pub trait FromServerFnError:
})
}
/// 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| {
/// Deserializes the custom error type from a [`&str`].
fn de(data: Bytes) -> Self {
Self::Encoder::decode(data).unwrap_or_else(|e| {
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()
})
}

View File

@@ -132,6 +132,7 @@ pub use ::bytes as bytes_export;
#[cfg(feature = "generic")]
#[doc(hidden)]
pub use ::http as http_export;
use base64::{engine::general_purpose::STANDARD_NO_PAD, DecodeError, Engine};
// re-exported to make it possible to implement a custom Client without adding a separate
// dependency on `bytes`
pub use bytes::Bytes;
@@ -473,10 +474,9 @@ where
let location = res.location();
let has_redirect_header = res.has_redirect();
// if it returns an error status, deserialize the error using FromStr
// if it returns an error status, deserialize the error using the error's decoder.
let res = if (400..=599).contains(&status) {
let text = res.try_into_string().await?;
Err(E::de(&text))
Err(E::de(res.try_into_bytes().await?))
} else {
// otherwise, deserialize the body as is
let output = Output::from_res(res).await?;
@@ -643,7 +643,7 @@ where
)
})
}
Err(err) => Err(err),
Err(err) => Err(InputStreamError::de(err)),
});
let boxed = Box::pin(input)
as Pin<
@@ -657,12 +657,13 @@ where
let output = server_fn(input.into()).await?;
let output = output.stream.map(|output| match output {
Ok(output) => OutputEncoding::encode(output).map_err(|e| {
Ok(output) => OutputEncoding::encode(&output).map_err(|e| {
OutputStreamError::from_server_fn_error(
ServerFnErrorErr::Serialization(e.to_string()),
)
.ser()
}),
Err(err) => Err(err),
Err(err) => Err(err.ser()),
});
Server::spawn(async move {
@@ -695,15 +696,19 @@ where
pin_mut!(sink);
while let Some(input) = input.stream.next().await {
if sink
.send(input.and_then(|input| {
InputEncoding::encode(input).map_err(|e| {
InputStreamError::from_server_fn_error(
ServerFnErrorErr::Serialization(
e.to_string(),
),
)
})
}))
.send(
input
.and_then(|input| {
InputEncoding::encode(&input).map_err(|e| {
InputStreamError::from_server_fn_error(
ServerFnErrorErr::Serialization(
e.to_string(),
),
)
})
})
.map_err(|e| e.ser()),
)
.await
.is_err()
{
@@ -720,7 +725,7 @@ where
ServerFnErrorErr::Deserialization(e.to_string()),
)
}),
Err(err) => Err(err),
Err(err) => Err(OutputStreamError::de(err)),
});
let boxed = Box::pin(stream)
as Pin<
@@ -735,19 +740,51 @@ where
}
}
/// Encode format type
pub enum Format {
/// Binary representation
Binary,
/// utf-8 compatible text representation
Text,
}
/// A trait for types with an associated content type.
pub trait ContentType {
/// The MIME type of the data.
const CONTENT_TYPE: &'static str;
}
/// Data format representation
pub trait FormatType {
/// The representation type
const FORMAT_TYPE: Format;
/// Encodes data into a string.
fn into_encoded_string(bytes: Bytes) -> String {
match Self::FORMAT_TYPE {
Format::Binary => STANDARD_NO_PAD.encode(bytes),
Format::Text => String::from_utf8(bytes.into())
.expect("Valid text format type with utf-8 comptabile string"),
}
}
/// Decodes string to bytes
fn from_encoded_string(data: &str) -> Result<Bytes, DecodeError> {
match Self::FORMAT_TYPE {
Format::Binary => {
STANDARD_NO_PAD.decode(data).map(|data| data.into())
}
Format::Text => Ok(Bytes::copy_from_slice(data.as_bytes())),
}
}
}
/// A trait for types that can be encoded into a bytes for a request body.
pub trait Encodes<T>: ContentType {
pub trait Encodes<T>: ContentType + FormatType {
/// The error type that can be returned if the encoding fails.
type Error: Display;
type Error: Display + Debug;
/// Encodes the given value into a bytes.
fn encode(output: T) -> Result<Bytes, Self::Error>;
fn encode(output: &T) -> Result<Bytes, Self::Error>;
}
/// A trait for types that can be decoded from a bytes for a response body.
@@ -789,7 +826,7 @@ pub struct ServerFnTraitObj<Req, Res> {
method: Method,
handler: fn(Req) -> Pin<Box<dyn Future<Output = Res> + Send>>,
middleware: fn() -> MiddlewareSet<Req, Res>,
ser: fn(ServerFnErrorErr) -> String,
ser: fn(ServerFnErrorErr) -> Bytes,
}
impl<Req, Res> ServerFnTraitObj<Req, Res> {
@@ -865,7 +902,7 @@ where
fn run(
&mut self,
req: Req,
_ser: fn(ServerFnErrorErr) -> String,
_ser: fn(ServerFnErrorErr) -> Bytes,
) -> Pin<Box<dyn Future<Output = Res> + Send>> {
let handler = self.handler;
Box::pin(async move { handler(req).await })

View File

@@ -1,4 +1,5 @@
use crate::error::ServerFnErrorErr;
use bytes::Bytes;
use std::{future::Future, pin::Pin};
/// An abstraction over a middleware layer, which can be used to add additional
@@ -11,7 +12,7 @@ pub trait Layer<Req, Res>: Send + Sync + 'static {
/// A type-erased service, which takes an HTTP request and returns a response.
pub struct BoxedService<Req, Res> {
/// A function that converts a [`ServerFnErrorErr`] into a string.
pub ser: fn(ServerFnErrorErr) -> String,
pub ser: fn(ServerFnErrorErr) -> Bytes,
/// The inner service.
pub service: Box<dyn Service<Req, Res> + Send>,
}
@@ -19,7 +20,7 @@ pub struct BoxedService<Req, Res> {
impl<Req, Res> BoxedService<Req, Res> {
/// Constructs a type-erased service from this service.
pub fn new(
ser: fn(ServerFnErrorErr) -> String,
ser: fn(ServerFnErrorErr) -> Bytes,
service: impl Service<Req, Res> + Send + 'static,
) -> Self {
Self {
@@ -43,7 +44,7 @@ pub trait Service<Request, Response> {
fn run(
&mut self,
req: Request,
ser: fn(ServerFnErrorErr) -> String,
ser: fn(ServerFnErrorErr) -> Bytes,
) -> Pin<Box<dyn Future<Output = Response> + Send>>;
}
@@ -52,6 +53,7 @@ mod axum {
use super::{BoxedService, Service};
use crate::{error::ServerFnErrorErr, response::Res, ServerFnError};
use axum::body::Body;
use bytes::Bytes;
use http::{Request, Response};
use std::{future::Future, pin::Pin};
@@ -64,7 +66,7 @@ mod axum {
fn run(
&mut self,
req: Request<Body>,
ser: fn(ServerFnErrorErr) -> String,
ser: fn(ServerFnErrorErr) -> Bytes,
) -> Pin<Box<dyn Future<Output = Response<Body>> + Send>> {
let path = req.uri().path().to_string();
let inner = self.call(req);
@@ -129,6 +131,7 @@ mod actix {
response::{actix::ActixResponse, Res},
};
use actix_web::{HttpRequest, HttpResponse};
use bytes::Bytes;
use std::{future::Future, pin::Pin};
impl<S> super::Service<HttpRequest, HttpResponse> for S
@@ -140,7 +143,7 @@ mod actix {
fn run(
&mut self,
req: HttpRequest,
ser: fn(ServerFnErrorErr) -> String,
ser: fn(ServerFnErrorErr) -> Bytes,
) -> Pin<Box<dyn Future<Output = HttpResponse> + Send>> {
let path = req.uri().path().to_string();
let inner = self.call(req);
@@ -163,7 +166,7 @@ mod actix {
fn run(
&mut self,
req: ActixRequest,
ser: fn(ServerFnErrorErr) -> String,
ser: fn(ServerFnErrorErr) -> Bytes,
) -> Pin<Box<dyn Future<Output = ActixResponse> + Send>> {
let path = req.0 .0.uri().path().to_string();
let inner = self.call(req.0.take().0);

View File

@@ -99,12 +99,14 @@ where
fn try_into_stream(
self,
) -> Result<impl Stream<Item = Result<Bytes, Error>> + Send, Error> {
) -> Result<impl Stream<Item = Result<Bytes, Bytes>> + Send, Error> {
let payload = self.0.take().1;
let stream = payload.map(|res| {
res.map_err(|e| {
ServerFnErrorErr::Deserialization(e.to_string())
.into_app_error()
Error::from_server_fn_error(ServerFnErrorErr::Deserialization(
e.to_string(),
))
.ser()
})
});
Ok(SendWrapper::new(stream))
@@ -114,8 +116,8 @@ where
self,
) -> Result<
(
impl Stream<Item = Result<Bytes, InputStreamError>> + Send + 'static,
impl futures::Sink<Result<Bytes, OutputStreamError>> + Send + 'static,
impl Stream<Item = Result<Bytes, Bytes>> + Send + 'static,
impl futures::Sink<Result<Bytes, Bytes>> + Send + 'static,
Self::WebsocketResponse,
),
Error,
@@ -131,9 +133,7 @@ where
let (mut response_stream_tx, response_stream_rx) =
futures::channel::mpsc::channel(2048);
let (response_sink_tx, mut response_sink_rx) =
futures::channel::mpsc::channel::<Result<Bytes, OutputStreamError>>(
2048,
);
futures::channel::mpsc::channel::<Result<Bytes, Bytes>>(2048);
actix_web::rt::spawn(async move {
loop {
@@ -145,11 +145,11 @@ where
match incoming {
Ok(message) => {
if let Err(err) = session.binary(message).await {
_ = response_stream_tx.start_send(Err(InputStreamError::from_server_fn_error(ServerFnErrorErr::Request(err.to_string()))));
_ = response_stream_tx.start_send(Err(InputStreamError::from_server_fn_error(ServerFnErrorErr::Request(err.to_string())).ser()));
}
}
Err(err) => {
_ = response_stream_tx.start_send(Err(InputStreamError::from_server_fn_error(ServerFnErrorErr::ServerError(err.ser()))));
_ = response_stream_tx.start_send(Err(err));
}
}
},
@@ -175,7 +175,7 @@ where
Ok(_other) => {
}
Err(e) => {
_ = response_stream_tx.start_send(Err(InputStreamError::from_server_fn_error(ServerFnErrorErr::Response(e.to_string()))));
_ = response_stream_tx.start_send(Err(InputStreamError::from_server_fn_error(ServerFnErrorErr::Response(e.to_string())).ser()));
}
}
}

View File

@@ -62,12 +62,14 @@ where
fn try_into_stream(
self,
) -> Result<impl Stream<Item = Result<Bytes, Error>> + Send + 'static, Error>
) -> Result<impl Stream<Item = Result<Bytes, Bytes>> + Send + 'static, Error>
{
Ok(self.into_body().into_data_stream().map(|chunk| {
chunk.map_err(|e| {
ServerFnErrorErr::Deserialization(e.to_string())
.into_app_error()
Error::from_server_fn_error(ServerFnErrorErr::Deserialization(
e.to_string(),
))
.ser()
})
}))
}
@@ -76,8 +78,8 @@ where
self,
) -> Result<
(
impl Stream<Item = Result<Bytes, InputStreamError>> + Send + 'static,
impl Sink<Result<Bytes, OutputStreamError>> + Send + 'static,
impl Stream<Item = Result<Bytes, Bytes>> + Send + 'static,
impl Sink<Result<Bytes, Bytes>> + Send + 'static,
Self::WebsocketResponse,
),
Error,
@@ -87,9 +89,9 @@ where
Err::<
(
futures::stream::Once<
std::future::Ready<Result<Bytes, InputStreamError>>,
std::future::Ready<Result<Bytes, Bytes>>,
>,
futures::sink::Drain<Result<Bytes, OutputStreamError>>,
futures::sink::Drain<Result<Bytes, Bytes>>,
Self::WebsocketResponse,
),
Error,
@@ -117,14 +119,12 @@ where
let (mut outgoing_tx, outgoing_rx) =
futures::channel::mpsc::channel(2048);
let (incoming_tx, mut incoming_rx) =
futures::channel::mpsc::channel::<
Result<Bytes, OutputStreamError>,
>(2048);
futures::channel::mpsc::channel::<Result<Bytes, Bytes>>(2048);
let response = upgrade
.on_failed_upgrade({
let mut outgoing_tx = outgoing_tx.clone();
move |err: axum::Error| {
_ = outgoing_tx.start_send(Err(InputStreamError::from_server_fn_error(ServerFnErrorErr::Response(err.to_string()))));
_ = outgoing_tx.start_send(Err(InputStreamError::from_server_fn_error(ServerFnErrorErr::Response(err.to_string())).ser()));
}
})
.on_upgrade(|mut session| async move {
@@ -137,11 +137,11 @@ where
match incoming {
Ok(message) => {
if let Err(err) = session.send(Message::Binary(message)).await {
_ = outgoing_tx.start_send(Err(InputStreamError::from_server_fn_error(ServerFnErrorErr::Request(err.to_string()))));
_ = outgoing_tx.start_send(Err(InputStreamError::from_server_fn_error(ServerFnErrorErr::Request(err.to_string())).ser()));
}
}
Err(err) => {
_ = outgoing_tx.start_send(Err(InputStreamError::from_server_fn_error(ServerFnErrorErr::ServerError(err.ser()))));
_ = outgoing_tx.start_send(Err(err));
}
}
},
@@ -161,7 +161,7 @@ where
}
Ok(_other) => {}
Err(e) => {
_ = outgoing_tx.start_send(Err(InputStreamError::from_server_fn_error(ServerFnErrorErr::Response(e.to_string()))));
_ = outgoing_tx.start_send(Err(InputStreamError::from_server_fn_error(ServerFnErrorErr::Response(e.to_string())).ser()));
}
}
}

View File

@@ -45,7 +45,7 @@ where
fn try_into_stream(
self,
) -> Result<impl Stream<Item = Result<Bytes, Error>> + Send + 'static, Error>
) -> Result<impl Stream<Item = Result<Bytes, Bytes>> + Send + 'static, Error>
{
Ok(stream::iter(self.into_body())
.ready_chunks(16)
@@ -78,18 +78,16 @@ where
self,
) -> Result<
(
impl Stream<Item = Result<Bytes, InputStreamError>> + Send + 'static,
impl Sink<Result<Bytes, OutputStreamError>> + Send + 'static,
impl Stream<Item = Result<Bytes, Bytes>> + Send + 'static,
impl Sink<Result<Bytes, Bytes>> + Send + 'static,
Self::WebsocketResponse,
),
Error,
> {
Err::<
(
futures::stream::Once<
std::future::Ready<Result<Bytes, InputStreamError>>,
>,
futures::sink::Drain<Result<Bytes, OutputStreamError>>,
futures::stream::Once<std::future::Ready<Result<Bytes, Bytes>>>,
futures::sink::Drain<Result<Bytes, Bytes>>,
Self::WebsocketResponse,
),
_,

View File

@@ -350,7 +350,7 @@ where
/// Attempts to convert the body of the request into a stream of bytes.
fn try_into_stream(
self,
) -> Result<impl Stream<Item = Result<Bytes, Error>> + Send + 'static, Error>;
) -> Result<impl Stream<Item = Result<Bytes, Bytes>> + Send + 'static, Error>;
/// Attempts to convert the body of the request into a websocket handle.
#[allow(clippy::type_complexity)]
@@ -359,8 +359,8 @@ where
) -> impl Future<
Output = Result<
(
impl Stream<Item = Result<Bytes, InputStreamError>> + Send + 'static,
impl Sink<Result<Bytes, OutputStreamError>> + Send + 'static,
impl Stream<Item = Result<Bytes, Bytes>> + Send + 'static,
impl Sink<Result<Bytes, Bytes>> + Send + 'static,
Self::WebsocketResponse,
),
Error,
@@ -406,7 +406,7 @@ where
fn try_into_stream(
self,
) -> Result<impl Stream<Item = Result<Bytes, Error>> + Send, Error> {
) -> Result<impl Stream<Item = Result<Bytes, Bytes>> + Send, Error> {
Ok(futures::stream::once(async { unreachable!() }))
}
@@ -414,8 +414,8 @@ where
self,
) -> Result<
(
impl Stream<Item = Result<Bytes, InputStreamError>> + Send + 'static,
impl Sink<Result<Bytes, OutputStreamError>> + Send + 'static,
impl Stream<Item = Result<Bytes, Bytes>> + Send + 'static,
impl Sink<Result<Bytes, Bytes>> + Send + 'static,
Self::WebsocketResponse,
),
Error,
@@ -423,10 +423,8 @@ where
#[allow(unreachable_code)]
Err::<
(
futures::stream::Once<
std::future::Ready<Result<Bytes, InputStreamError>>,
>,
futures::sink::Drain<Result<Bytes, OutputStreamError>>,
futures::stream::Once<std::future::Ready<Result<Bytes, Bytes>>>,
futures::sink::Drain<Result<Bytes, Bytes>>,
Self::WebsocketResponse,
),
_,

View File

@@ -51,13 +51,14 @@ where
fn try_into_stream(
self,
) -> Result<
impl Stream<Item = Result<Bytes, ServerFnError>> + Send + 'static,
E,
> {
) -> Result<impl Stream<Item = Result<Bytes, Bytes>> + Send + 'static, E>
{
Ok(self.into_body().into_data_stream().map(|chunk| {
chunk.map_err(|e| {
ServerFnErrorErr::Deserialization(e.to_string()).into()
E::from_server_fn_error(ServerFnErrorErr::Deserialization(
e.to_string(),
))
.ser()
})
}))
}

View File

@@ -58,19 +58,21 @@ where
fn try_from_stream(
content_type: &str,
data: impl Stream<Item = Result<Bytes, E>> + 'static,
data: impl Stream<Item = Result<Bytes, Bytes>> + 'static,
) -> Result<Self, E> {
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(ServerFnErrorWrapper))),
.streaming(data.map(|data| {
data.map_err(|e| ServerFnErrorWrapper(E::de(e)))
})),
)))
}
}
impl Res for ActixResponse {
fn error_response(path: &str, err: String) -> Self {
fn error_response(path: &str, err: Bytes) -> Self {
ActixResponse(SendWrapper::new(
HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR)
.append_header((SERVER_FN_ERROR_HEADER, path))

View File

@@ -40,14 +40,17 @@ impl<E: FromServerFnError> ClientRes<E> for BrowserResponse {
fn try_into_stream(
self,
) -> Result<impl Stream<Item = Result<Bytes, E>> + Send + 'static, E> {
) -> Result<impl Stream<Item = Result<Bytes, Bytes>> + 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(ServerFnErrorErr::Request(format!("{e:?}"))
.into_app_error())
Err(E::from_server_fn_error(ServerFnErrorErr::Request(
format!("{e:?}"),
))
.ser())
}
Ok(data) => {
let data = data.unchecked_into::<Uint8Array>();

View File

@@ -41,6 +41,12 @@ impl From<String> for Body {
}
}
impl From<Bytes> for Body {
fn from(value: Bytes) -> Self {
Body::Sync(value)
}
}
impl<E> TryRes<E> for Response<Body>
where
E: Send + Sync + FromServerFnError,
@@ -69,14 +75,15 @@ where
fn try_from_stream(
content_type: &str,
data: impl Stream<Item = Result<Bytes, E>> + Send + 'static,
data: impl Stream<Item = Result<Bytes, Bytes>> + Send + 'static,
) -> Result<Self, E> {
let builder = http::Response::builder();
builder
.status(200)
.header(http::header::CONTENT_TYPE, content_type)
.body(Body::Async(Box::pin(
data.map_err(ServerFnErrorWrapper).map_err(Error::from),
data.map_err(|e| ServerFnErrorWrapper(E::de(e)))
.map_err(Error::from),
)))
.map_err(|e| {
ServerFnErrorErr::Response(e.to_string()).into_app_error()
@@ -85,7 +92,7 @@ where
}
impl Res for Response<Body> {
fn error_response(path: &str, err: String) -> Self {
fn error_response(path: &str, err: Bytes) -> Self {
Response::builder()
.status(http::StatusCode::INTERNAL_SERVER_ERROR)
.header(SERVER_FN_ERROR_HEADER, path)

View File

@@ -36,9 +36,10 @@ where
fn try_from_stream(
content_type: &str,
data: impl Stream<Item = Result<Bytes, E>> + Send + 'static,
data: impl Stream<Item = Result<Bytes, Bytes>> + Send + 'static,
) -> Result<Self, E> {
let body = Body::from_stream(data.map_err(|e| ServerFnErrorWrapper(e)));
let body =
Body::from_stream(data.map_err(|e| ServerFnErrorWrapper(E::de(e))));
let builder = http::Response::builder();
builder
.status(200)
@@ -51,7 +52,7 @@ where
}
impl Res for Response<Body> {
fn error_response(path: &str, err: String) -> Self {
fn error_response(path: &str, err: Bytes) -> Self {
Response::builder()
.status(http::StatusCode::INTERNAL_SERVER_ERROR)
.header(SERVER_FN_ERROR_HEADER, path)

View File

@@ -31,14 +31,14 @@ where
/// Attempts to convert a stream of bytes into an HTTP response.
fn try_from_stream(
content_type: &str,
data: impl Stream<Item = Result<Bytes, E>> + Send + 'static,
data: impl Stream<Item = Result<Bytes, Bytes>> + Send + 'static,
) -> Result<Self, E>;
}
/// 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: String) -> Self;
fn error_response(path: &str, err: Bytes) -> Self;
/// Redirect the response by setting a 302 code and Location header.
fn redirect(&mut self, path: &str);
@@ -55,7 +55,10 @@ pub trait ClientRes<E> {
/// Attempts to extract a binary stream from an HTTP response.
fn try_into_stream(
self,
) -> Result<impl Stream<Item = Result<Bytes, E>> + Send + Sync + 'static, E>;
) -> Result<
impl Stream<Item = Result<Bytes, Bytes>> + Send + Sync + 'static,
E,
>;
/// HTTP status code of the response.
fn status(&self) -> u16;
@@ -89,14 +92,14 @@ impl<E> TryRes<E> for BrowserMockRes {
fn try_from_stream(
_content_type: &str,
_data: impl Stream<Item = Result<Bytes, E>>,
_data: impl Stream<Item = Result<Bytes, Bytes>>,
) -> Result<Self, E> {
unreachable!()
}
}
impl Res for BrowserMockRes {
fn error_response(_path: &str, _err: String) -> Self {
fn error_response(_path: &str, _err: Bytes) -> Self {
unreachable!()
}

View File

@@ -19,9 +19,11 @@ impl<E: FromServerFnError> ClientRes<E> for Response {
fn try_into_stream(
self,
) -> Result<impl Stream<Item = Result<Bytes, E>> + Send + 'static, E> {
) -> Result<impl Stream<Item = Result<Bytes, Bytes>> + Send + 'static, E>
{
Ok(self.bytes_stream().map_err(|e| {
ServerFnErrorErr::Response(e.to_string()).into_app_error()
E::from_server_fn_error(ServerFnErrorErr::Response(e.to_string()))
.ser()
}))
}

View File

@@ -1,7 +1,12 @@
use server_fn::{
codec::JsonEncoding,
error::{FromServerFnError, ServerFnErrorErr},
};
use server_fn_macro_default::server;
use server_fn::error::{FromServerFnError, ServerFnErrorErr};
#[derive(Debug, thiserror::Error, Clone, serde::Serialize, serde::Deserialize)]
#[derive(
Debug, thiserror::Error, Clone, serde::Serialize, serde::Deserialize,
)]
pub enum CustomError {
#[error("error a")]
A,
@@ -10,6 +15,8 @@ pub enum CustomError {
}
impl FromServerFnError for CustomError {
type Encoder = JsonEncoding;
fn from_server_fn_error(_: ServerFnErrorErr) -> Self {
Self::A
}
@@ -20,4 +27,4 @@ pub async fn full_alias_result() -> CustomError {
CustomError::A
}
fn main() {}
fn main() {}

View File

@@ -1,7 +1,7 @@
error[E0277]: CustomError is not a `Result` or aliased `Result`. Server functions must return a `Result` or aliased `Result`.
--> tests/invalid/not_result.rs:18:1
--> tests/invalid/not_result.rs:25:1
|
18 | #[server]
25 | #[server]
| ^^^^^^^^^ Must return a `Result` or aliased `Result`.
|
= help: the trait `ServerFnMustReturnResult` is not implemented for `CustomError`
@@ -9,9 +9,9 @@ error[E0277]: CustomError is not a `Result` or aliased `Result`. Server function
= help: the trait `ServerFnMustReturnResult` is implemented for `Result<T, E>`
error[E0271]: expected `impl Future<Output = CustomError>` to be a future that resolves to `Result<_, _>`, but it resolves to `CustomError`
--> tests/invalid/not_result.rs:18:1
--> tests/invalid/not_result.rs:25:1
|
18 | #[server]
25 | #[server]
| ^^^^^^^^^ expected `Result<_, _>`, found `CustomError`
|
= note: expected enum `Result<_, _>`
@@ -23,9 +23,9 @@ note: required by a bound in `ServerFn::{synthetic#0}`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `ServerFn::{synthetic#0}`
error[E0277]: CustomError is not a `Result` or aliased `Result`. Server functions must return a `Result` or aliased `Result`.
--> tests/invalid/not_result.rs:18:1
--> tests/invalid/not_result.rs:25:1
|
18 | #[server]
25 | #[server]
| ^^^^^^^^^ Must return a `Result` or aliased `Result`.
|
= help: the trait `ServerFnMustReturnResult` is not implemented for `CustomError`
@@ -34,9 +34,9 @@ error[E0277]: CustomError is not a `Result` or aliased `Result`. Server function
= note: this error originates in the attribute macro `server` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0277]: CustomError is not a `Result` or aliased `Result`. Server functions must return a `Result` or aliased `Result`.
--> tests/invalid/not_result.rs:18:1
--> tests/invalid/not_result.rs:25:1
|
18 | #[server]
25 | #[server]
| ^^^^^^^^^ Must return a `Result` or aliased `Result`.
|
= help: the trait `ServerFnMustReturnResult` is not implemented for `CustomError`
@@ -44,14 +44,14 @@ error[E0277]: CustomError is not a `Result` or aliased `Result`. Server function
= help: the trait `ServerFnMustReturnResult` is implemented for `Result<T, E>`
error[E0308]: mismatched types
--> tests/invalid/not_result.rs:18:1
--> tests/invalid/not_result.rs:25:1
|
18 | #[server]
25 | #[server]
| ^^^^^^^^^ expected `CustomError`, found `Result<_, _>`
|
= note: expected enum `CustomError`
found enum `Result<_, _>`
help: consider using `Result::expect` to unwrap the `Result<_, _>` value, panicking if the value is a `Result::Err`
|
18 | #[server].expect("REASON")
25 | #[server].expect("REASON")
| +++++++++++++++++

View File

@@ -1,7 +1,12 @@
use server_fn::{
codec::JsonEncoding,
error::{FromServerFnError, ServerFnErrorErr},
};
use server_fn_macro_default::server;
use server_fn::error::{FromServerFnError, ServerFnErrorErr};
#[derive(Debug, thiserror::Error, Clone, serde::Serialize, serde::Deserialize)]
#[derive(
Debug, thiserror::Error, Clone, serde::Serialize, serde::Deserialize,
)]
pub enum CustomError {
#[error("error a")]
ErrorA,
@@ -10,6 +15,8 @@ pub enum CustomError {
}
impl FromServerFnError for CustomError {
type Encoder = JsonEncoding;
fn from_server_fn_error(_: ServerFnErrorErr) -> Self {
Self::ErrorA
}
@@ -22,4 +29,4 @@ pub async fn full_alias_result() -> FullAlias {
Ok("hello".to_string())
}
fn main() {}
fn main() {}

View File

@@ -1,7 +1,12 @@
use server_fn::{
codec::JsonEncoding,
error::{FromServerFnError, ServerFnErrorErr},
};
use server_fn_macro_default::server;
use server_fn::error::{FromServerFnError, ServerFnErrorErr};
#[derive(Debug, thiserror::Error, Clone, serde::Serialize, serde::Deserialize)]
#[derive(
Debug, thiserror::Error, Clone, serde::Serialize, serde::Deserialize,
)]
pub enum CustomError {
#[error("error a")]
ErrorA,
@@ -10,6 +15,8 @@ pub enum CustomError {
}
impl FromServerFnError for CustomError {
type Encoder = JsonEncoding;
fn from_server_fn_error(_: ServerFnErrorErr) -> Self {
Self::ErrorA
}
@@ -20,4 +27,4 @@ pub async fn no_alias_result() -> Result<String, CustomError> {
Ok("hello".to_string())
}
fn main() {}
fn main() {}

View File

@@ -1,7 +1,12 @@
use server_fn::{
codec::JsonEncoding,
error::{FromServerFnError, ServerFnErrorErr},
};
use server_fn_macro_default::server;
use server_fn::error::{FromServerFnError, ServerFnErrorErr};
#[derive(Debug, thiserror::Error, Clone, serde::Serialize, serde::Deserialize)]
#[derive(
Debug, thiserror::Error, Clone, serde::Serialize, serde::Deserialize,
)]
pub enum CustomError {
#[error("error a")]
ErrorA,
@@ -10,6 +15,8 @@ pub enum CustomError {
}
impl FromServerFnError for CustomError {
type Encoder = JsonEncoding;
fn from_server_fn_error(_: ServerFnErrorErr) -> Self {
Self::ErrorA
}
@@ -22,4 +29,4 @@ pub async fn part_alias_result() -> PartAlias<String> {
Ok("hello".to_string())
}
fn main() {}
fn main() {}