mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-27 09:54:41 -05:00
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:
@@ -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()),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)),
|
||||
}))))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
_,
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
_,
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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!()
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
@@ -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")
|
||||
| +++++++++++++++++
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
Reference in New Issue
Block a user