feat: allow any type that implements FromServerFnError as a replacement of the ServerFnError in server_fn (#3274)

This commit is contained in:
Ryo Hirayama
2025-01-18 03:30:12 +09:00
committed by GitHub
parent 9fd2a75da4
commit cfe925d58f
37 changed files with 1054 additions and 909 deletions

1
Cargo.lock generated
View File

@@ -3278,6 +3278,7 @@ version = "0.7.4"
dependencies = [
"actix-web",
"axum",
"base64",
"bytes",
"ciborium",
"const_format",

View File

@@ -9,8 +9,9 @@ use server_fn::{
MultipartFormData, Postcard, Rkyv, SerdeLite, StreamingText,
TextStream,
},
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
request::{browser::BrowserRequest, ClientReq, Req},
response::{browser::BrowserResponse, ClientRes, Res},
response::{browser::BrowserResponse, ClientRes, TryRes},
};
use std::future::Future;
#[cfg(feature = "ssr")]
@@ -652,32 +653,72 @@ pub fn FileWatcher() -> impl IntoView {
/// implementations if you'd like. However, it's much lighter weight to use something like `strum`
/// simply to generate those trait implementations.
#[server]
pub async fn ascii_uppercase(
text: String,
) -> Result<String, ServerFnError<InvalidArgument>> {
pub async fn ascii_uppercase(text: String) -> Result<String, MyErrors> {
other_error()?;
Ok(ascii_uppercase_inner(text)?)
}
pub fn other_error() -> Result<(), String> {
Ok(())
}
pub fn ascii_uppercase_inner(text: String) -> Result<String, InvalidArgument> {
if text.len() < 5 {
Err(InvalidArgument::TooShort.into())
Err(InvalidArgument::TooShort)
} else if text.len() > 15 {
Err(InvalidArgument::TooLong.into())
Err(InvalidArgument::TooLong)
} else if text.is_ascii() {
Ok(text.to_ascii_uppercase())
} else {
Err(InvalidArgument::NotAscii.into())
Err(InvalidArgument::NotAscii)
}
}
#[server]
pub async fn ascii_uppercase_classic(
text: String,
) -> Result<String, ServerFnError<InvalidArgument>> {
Ok(ascii_uppercase_inner(text)?)
}
// The EnumString and Display derive macros are provided by strum
#[derive(Debug, Clone, EnumString, Display)]
#[derive(Debug, Clone, Display, EnumString, Serialize, Deserialize)]
pub enum InvalidArgument {
TooShort,
TooLong,
NotAscii,
}
#[derive(Debug, Clone, Display, Serialize, Deserialize)]
pub enum MyErrors {
InvalidArgument(InvalidArgument),
ServerFnError(ServerFnErrorErr),
Other(String),
}
impl From<InvalidArgument> for MyErrors {
fn from(value: InvalidArgument) -> Self {
MyErrors::InvalidArgument(value)
}
}
impl From<String> for MyErrors {
fn from(value: String) -> Self {
MyErrors::Other(value)
}
}
impl FromServerFnError for MyErrors {
fn from_server_fn_error(value: ServerFnErrorErr) -> Self {
MyErrors::ServerFnError(value)
}
}
#[component]
pub fn CustomErrorTypes() -> impl IntoView {
let input_ref = NodeRef::<Input>::new();
let (result, set_result) = signal(None);
let (result_classic, set_result_classic) = signal(None);
view! {
<h3>Using custom error types</h3>
@@ -692,14 +733,17 @@ pub fn CustomErrorTypes() -> impl IntoView {
<button on:click=move |_| {
let value = input_ref.get().unwrap().value();
spawn_local(async move {
let data = ascii_uppercase(value).await;
let data = ascii_uppercase(value.clone()).await;
let data_classic = ascii_uppercase_classic(value).await;
set_result.set(Some(data));
set_result_classic.set(Some(data_classic));
});
}>
"Submit"
</button>
<p>{move || format!("{:?}", result.get())}</p>
<p>{move || format!("{:?}", result_classic.get())}</p>
}
}
@@ -726,14 +770,12 @@ impl<T, Request, Err> IntoReq<Toml, Request, Err> for TomlEncoded<T>
where
Request: ClientReq<Err>,
T: Serialize,
Err: FromServerFnError,
{
fn into_req(
self,
path: &str,
accepts: &str,
) -> Result<Request, ServerFnError<Err>> {
let data = toml::to_string(&self.0)
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
fn into_req(self, path: &str, accepts: &str) -> Result<Request, Err> {
let data = toml::to_string(&self.0).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Request::try_new_post(path, Toml::CONTENT_TYPE, accepts, data)
}
}
@@ -742,23 +784,26 @@ impl<T, Request, Err> FromReq<Toml, Request, Err> for TomlEncoded<T>
where
Request: Req<Err> + Send,
T: DeserializeOwned,
Err: FromServerFnError,
{
async fn from_req(req: Request) -> Result<Self, ServerFnError<Err>> {
async fn from_req(req: Request) -> Result<Self, Err> {
let string_data = req.try_into_string().await?;
toml::from_str::<T>(&string_data)
.map(TomlEncoded)
.map_err(|e| ServerFnError::Args(e.to_string()))
.map_err(|e| ServerFnErrorErr::Args(e.to_string()).into_app_error())
}
}
impl<T, Response, Err> IntoRes<Toml, Response, Err> for TomlEncoded<T>
where
Response: Res<Err>,
Response: TryRes<Err>,
T: Serialize + Send,
Err: FromServerFnError,
{
async fn into_res(self) -> Result<Response, ServerFnError<Err>> {
let data = toml::to_string(&self.0)
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
async fn into_res(self) -> Result<Response, Err> {
let data = toml::to_string(&self.0).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Response::try_from_string(Toml::CONTENT_TYPE, data)
}
}
@@ -767,12 +812,13 @@ impl<T, Response, Err> FromRes<Toml, Response, Err> for TomlEncoded<T>
where
Response: ClientRes<Err> + Send,
T: DeserializeOwned,
Err: FromServerFnError,
{
async fn from_res(res: Response) -> Result<Self, ServerFnError<Err>> {
async fn from_res(res: Response) -> Result<Self, Err> {
let data = res.try_into_string().await?;
toml::from_str(&data)
.map(TomlEncoded)
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
toml::from_str(&data).map(TomlEncoded).map_err(|e| {
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()
})
}
}
@@ -835,7 +881,10 @@ pub fn CustomClientExample() -> impl IntoView {
pub struct CustomClient;
// Implement the `Client` trait for it.
impl<CustErr> Client<CustErr> for CustomClient {
impl<E> Client<E> for CustomClient
where
E: FromServerFnError,
{
// BrowserRequest and BrowserResponse are the defaults used by other server functions.
// They are wrappers for the underlying Web Fetch API types.
type Request = BrowserRequest;
@@ -844,8 +893,7 @@ pub fn CustomClientExample() -> impl IntoView {
// Our custom `send()` implementation does all the work.
fn send(
req: Self::Request,
) -> impl Future<Output = Result<Self::Response, ServerFnError<CustErr>>>
+ Send {
) -> impl Future<Output = Result<Self::Response, E>> + Send {
// BrowserRequest derefs to the underlying Request type from gloo-net,
// so we can get access to the headers here
let headers = req.headers();

View File

@@ -369,7 +369,6 @@ pub fn handle_server_fns_with_context(
// actually run the server fn
let mut res = ActixResponse(
service
.0
.run(ActixRequest::from((req, payload)))
.await
.take(),

View File

@@ -368,8 +368,6 @@ async fn handle_server_fns_inner(
additional_context: impl Fn() + 'static + Clone + Send,
req: Request<Body>,
) -> impl IntoResponse {
use server_fn::middleware::Service;
let method = req.method().clone();
let path = req.uri().path().to_string();
let (req, parts) = generate_request_and_parts(req);

View File

@@ -3,7 +3,11 @@ use leptos_dom::helpers::window;
use leptos_server::{ServerAction, ServerMultiAction};
use serde::de::DeserializeOwned;
use server_fn::{
client::Client, codec::PostUrl, request::ClientReq, ServerFn, ServerFnError,
client::Client,
codec::PostUrl,
error::{IntoAppError, ServerFnErrorErr},
request::ClientReq,
ServerFn,
};
use tachys::{
either::Either,
@@ -121,9 +125,10 @@ where
"Error converting form field into server function \
arguments: {err:?}"
);
value.set(Some(Err(ServerFnError::Serialization(
value.set(Some(Err(ServerFnErrorErr::Serialization(
err.to_string(),
))));
)
.into_app_error())));
version.update(|n| *n += 1);
}
}
@@ -187,9 +192,10 @@ where
action.dispatch(new_input);
}
Err(err) => {
action.dispatch_sync(Err(ServerFnError::Serialization(
action.dispatch_sync(Err(ServerFnErrorErr::Serialization(
err.to_string(),
)));
)
.into_app_error()));
}
}
};

View File

@@ -172,7 +172,7 @@ pub mod prelude {
actions::*, computed::*, effect::*, graph::untrack, owner::*,
signal::*, wrappers::read::*,
};
pub use server_fn::{self, ServerFnError};
pub use server_fn::{self, error::ServerFnError};
pub use tachys::{
reactive_graph::{bind::BindAttribute, node_ref::*, Suspend},
view::{

View File

@@ -3,7 +3,7 @@ use reactive_graph::{
owner::use_context,
traits::DefinedAt,
};
use server_fn::{error::ServerFnErrorSerde, ServerFn, ServerFnError};
use server_fn::{error::FromServerFnError, ServerFn};
use std::{ops::Deref, panic::Location, sync::Arc};
/// An error that can be caused by a server action.
@@ -42,7 +42,7 @@ where
S: ServerFn + 'static,
S::Output: 'static,
{
inner: ArcAction<S, Result<S::Output, ServerFnError<S::Error>>>,
inner: ArcAction<S, Result<S::Output, S::Error>>,
#[cfg(any(debug_assertions, leptos_debuginfo))]
defined_at: &'static Location<'static>,
}
@@ -52,13 +52,14 @@ where
S: ServerFn + Clone + Send + Sync + 'static,
S::Output: Send + Sync + 'static,
S::Error: Send + Sync + 'static,
S::Error: FromServerFnError,
{
/// Creates a new [`ArcAction`] that will call the server function `S` when dispatched.
#[track_caller]
pub fn new() -> Self {
let err = use_context::<ServerActionError>().and_then(|error| {
(error.path() == S::PATH)
.then(|| ServerFnError::<S::Error>::de(error.err()))
.then(|| S::Error::de(error.err()))
.map(Err)
});
Self {
@@ -76,7 +77,7 @@ where
S: ServerFn + 'static,
S::Output: 'static,
{
type Target = ArcAction<S, Result<S::Output, ServerFnError<S::Error>>>;
type Target = ArcAction<S, Result<S::Output, S::Error>>;
fn deref(&self) -> &Self::Target {
&self.inner
@@ -131,7 +132,7 @@ where
S: ServerFn + 'static,
S::Output: 'static,
{
inner: Action<S, Result<S::Output, ServerFnError<S::Error>>>,
inner: Action<S, Result<S::Output, S::Error>>,
#[cfg(any(debug_assertions, leptos_debuginfo))]
defined_at: &'static Location<'static>,
}
@@ -146,7 +147,7 @@ where
pub fn new() -> Self {
let err = use_context::<ServerActionError>().and_then(|error| {
(error.path() == S::PATH)
.then(|| ServerFnError::<S::Error>::de(error.err()))
.then(|| S::Error::de(error.err()))
.map(Err)
});
Self {
@@ -182,15 +183,14 @@ where
S::Output: Send + Sync + 'static,
S::Error: Send + Sync + 'static,
{
type Target = Action<S, Result<S::Output, ServerFnError<S::Error>>>;
type Target = Action<S, Result<S::Output, S::Error>>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<S> From<ServerAction<S>>
for Action<S, Result<S::Output, ServerFnError<S::Error>>>
impl<S> From<ServerAction<S>> for Action<S, Result<S::Output, S::Error>>
where
S: ServerFn + 'static,
S::Output: 'static,

View File

@@ -2,7 +2,7 @@ use reactive_graph::{
actions::{ArcMultiAction, MultiAction},
traits::DefinedAt,
};
use server_fn::{ServerFn, ServerFnError};
use server_fn::ServerFn;
use std::{ops::Deref, panic::Location};
/// An [`ArcMultiAction`] that can be used to call a server function.
@@ -11,7 +11,7 @@ where
S: ServerFn + 'static,
S::Output: 'static,
{
inner: ArcMultiAction<S, Result<S::Output, ServerFnError<S::Error>>>,
inner: ArcMultiAction<S, Result<S::Output, S::Error>>,
#[cfg(any(debug_assertions, leptos_debuginfo))]
defined_at: &'static Location<'static>,
}
@@ -40,7 +40,7 @@ where
S: ServerFn + 'static,
S::Output: 'static,
{
type Target = ArcMultiAction<S, Result<S::Output, ServerFnError<S::Error>>>;
type Target = ArcMultiAction<S, Result<S::Output, S::Error>>;
fn deref(&self) -> &Self::Target {
&self.inner
@@ -95,13 +95,13 @@ where
S: ServerFn + 'static,
S::Output: 'static,
{
inner: MultiAction<S, Result<S::Output, ServerFnError<S::Error>>>,
inner: MultiAction<S, Result<S::Output, S::Error>>,
#[cfg(any(debug_assertions, leptos_debuginfo))]
defined_at: &'static Location<'static>,
}
impl<S> From<ServerMultiAction<S>>
for MultiAction<S, Result<S::Output, ServerFnError<S::Error>>>
for MultiAction<S, Result<S::Output, S::Error>>
where
S: ServerFn + 'static,
S::Output: 'static,
@@ -152,7 +152,7 @@ where
S::Output: 'static,
S::Error: 'static,
{
type Target = MultiAction<S, Result<S::Output, ServerFnError<S::Error>>>;
type Target = MultiAction<S, Result<S::Output, S::Error>>;
fn deref(&self) -> &Self::Target {
&self.inner

View File

@@ -53,6 +53,7 @@ bytes = "1.9"
http-body-util = { version = "0.1.2", optional = true }
rkyv = { version = "0.8.9", optional = true }
rmp-serde = { version = "1.3.0", optional = true }
base64 = { version = "0.22.1" }
# client
gloo-net = { version = "0.6.0", optional = true }

View File

@@ -1,4 +1,4 @@
use crate::{error::ServerFnError, request::ClientReq, response::ClientRes};
use crate::{request::ClientReq, response::ClientRes};
use std::{future::Future, sync::OnceLock};
static ROOT_URL: OnceLock<&'static str> = OnceLock::new();
@@ -21,16 +21,16 @@ pub fn get_server_url() -> &'static str {
/// This trait is implemented for things like a browser `fetch` request or for
/// the `reqwest` trait. It should almost never be necessary to implement it
/// yourself, unless youre trying to use an alternative HTTP crate on the client side.
pub trait Client<CustErr> {
pub trait Client<E> {
/// The type of a request sent by this client.
type Request: ClientReq<CustErr> + Send;
type Request: ClientReq<E> + Send;
/// The type of a response received by this client.
type Response: ClientRes<CustErr> + Send;
type Response: ClientRes<E> + Send;
/// Sends the request and receives a response.
fn send(
req: Self::Request,
) -> impl Future<Output = Result<Self::Response, ServerFnError<CustErr>>> + Send;
) -> impl Future<Output = Result<Self::Response, E>> + Send;
}
#[cfg(feature = "browser")]
@@ -38,24 +38,23 @@ pub trait Client<CustErr> {
pub mod browser {
use super::Client;
use crate::{
error::ServerFnError,
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
request::browser::{BrowserRequest, RequestInner},
response::browser::BrowserResponse,
};
use send_wrapper::SendWrapper;
use std::future::Future;
/// Implements [`Client`] for a `fetch` request in the browser.
/// Implements [`Client`] for a `fetch` request in the browser.
pub struct BrowserClient;
impl<CustErr> Client<CustErr> for BrowserClient {
impl<E: FromServerFnError> Client<E> for BrowserClient {
type Request = BrowserRequest;
type Response = BrowserResponse;
fn send(
req: Self::Request,
) -> impl Future<Output = Result<Self::Response, ServerFnError<CustErr>>>
+ Send {
) -> impl Future<Output = Result<Self::Response, E>> + Send {
SendWrapper::new(async move {
let req = req.0.take();
let RequestInner {
@@ -66,7 +65,10 @@ pub mod browser {
.send()
.await
.map(|res| BrowserResponse(SendWrapper::new(res)))
.map_err(|e| ServerFnError::Request(e.to_string()));
.map_err(|e| {
ServerFnErrorErr::Request(e.to_string())
.into_app_error()
});
// at this point, the future has successfully resolved without being dropped, so we
// can prevent the `AbortController` from firing
@@ -83,7 +85,10 @@ pub mod browser {
/// Implements [`Client`] for a request made by [`reqwest`].
pub mod reqwest {
use super::Client;
use crate::{error::ServerFnError, request::reqwest::CLIENT};
use crate::{
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
request::reqwest::CLIENT,
};
use futures::TryFutureExt;
use reqwest::{Request, Response};
use std::future::Future;
@@ -91,17 +96,16 @@ pub mod reqwest {
/// Implements [`Client`] for a request made by [`reqwest`].
pub struct ReqwestClient;
impl<CustErr> Client<CustErr> for ReqwestClient {
impl<E: FromServerFnError> Client<E> for ReqwestClient {
type Request = Request;
type Response = Response;
fn send(
req: Self::Request,
) -> impl Future<Output = Result<Self::Response, ServerFnError<CustErr>>>
+ Send {
CLIENT
.execute(req)
.map_err(|e| ServerFnError::Request(e.to_string()))
) -> impl Future<Output = Result<Self::Response, E>> + Send {
CLIENT.execute(req).map_err(|e| {
ServerFnErrorErr::Request(e.to_string()).into_app_error()
})
}
}
}

View File

@@ -1,8 +1,8 @@
use super::{Encoding, FromReq, FromRes, IntoReq, IntoRes};
use crate::{
error::ServerFnError,
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
request::{ClientReq, Req},
response::{ClientRes, Res},
response::{ClientRes, TryRes},
};
use bytes::Bytes;
use http::Method;
@@ -16,19 +16,17 @@ impl Encoding for Cbor {
const METHOD: Method = Method::POST;
}
impl<CustErr, T, Request> IntoReq<Cbor, Request, CustErr> for T
impl<E, T, Request> IntoReq<Cbor, Request, E> for T
where
Request: ClientReq<CustErr>,
Request: ClientReq<E>,
T: Serialize + Send,
E: FromServerFnError,
{
fn into_req(
self,
path: &str,
accepts: &str,
) -> Result<Request, ServerFnError<CustErr>> {
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
let mut buffer: Vec<u8> = Vec::new();
ciborium::ser::into_writer(&self, &mut buffer)
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
ciborium::ser::into_writer(&self, &mut buffer).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Request::try_new_post_bytes(
path,
accepts,
@@ -38,40 +36,44 @@ where
}
}
impl<CustErr, T, Request> FromReq<Cbor, Request, CustErr> for T
impl<E, T, Request> FromReq<Cbor, Request, E> for T
where
Request: Req<CustErr> + Send + 'static,
Request: Req<E> + Send + 'static,
T: DeserializeOwned,
E: FromServerFnError,
{
async fn from_req(req: Request) -> Result<Self, ServerFnError<CustErr>> {
async fn from_req(req: Request) -> Result<Self, E> {
let body_bytes = req.try_into_bytes().await?;
ciborium::de::from_reader(body_bytes.as_ref())
.map_err(|e| ServerFnError::Args(e.to_string()))
.map_err(|e| ServerFnErrorErr::Args(e.to_string()).into_app_error())
}
}
impl<CustErr, T, Response> IntoRes<Cbor, Response, CustErr> for T
impl<E, T, Response> IntoRes<Cbor, Response, E> for T
where
Response: Res<CustErr>,
Response: TryRes<E>,
T: Serialize + Send,
E: FromServerFnError,
{
async fn into_res(self) -> Result<Response, ServerFnError<CustErr>> {
async fn into_res(self) -> Result<Response, E> {
let mut buffer: Vec<u8> = Vec::new();
ciborium::ser::into_writer(&self, &mut buffer)
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
ciborium::ser::into_writer(&self, &mut buffer).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Response::try_from_bytes(Cbor::CONTENT_TYPE, Bytes::from(buffer))
}
}
impl<CustErr, T, Response> FromRes<Cbor, Response, CustErr> for T
impl<E, T, Response> FromRes<Cbor, Response, E> for T
where
Response: ClientRes<CustErr> + Send,
Response: ClientRes<E> + Send,
T: DeserializeOwned + Send,
E: FromServerFnError,
{
async fn from_res(res: Response) -> Result<Self, ServerFnError<CustErr>> {
async fn from_res(res: Response) -> Result<Self, E> {
let data = res.try_into_bytes().await?;
ciborium::de::from_reader(data.as_ref())
.map_err(|e| ServerFnError::Args(e.to_string()))
.map_err(|e| ServerFnErrorErr::Args(e.to_string()).into_app_error())
}
}
@@ -114,20 +116,20 @@ where
<ResponseBody as HttpBody>::Data: Send ,
<RequestBody as HttpBody>::Data: Send ,
{
async fn from_req(req: http::Request<RequestBody>) -> Result<Self, ServerFnError<CustErr>> {
async fn from_req(req: http::Request<RequestBody>) -> Result<Self, ServerFnError<E>> {
let (_parts, body) = req.into_parts();
let body_bytes = body
.collect()
.await
.map(|c| c.to_bytes())
.map_err(|e| ServerFnError::Deserialization(e.to_string()))?;
.map_err(|e| ServerFnErrorErr::Deserialization(e.to_string()).into())?;
let data = ciborium::de::from_reader(body_bytes.as_ref())
.map_err(|e| ServerFnError::Args(e.to_string()))?;
.map_err(|e| ServerFnErrorErr::Args(e.to_string()).into())?;
Ok(data)
}
async fn into_req(self) -> Result<http::Request<Body>, ServerFnError<CustErr>> {
async fn into_req(self) -> Result<http::Request<Body>, ServerFnError<E>> {
let mut buffer: Vec<u8> = Vec::new();
ciborium::ser::into_writer(&self, &mut buffer)?;
let req = http::Request::builder()
@@ -139,17 +141,17 @@ where
.body(Body::from(buffer))?;
Ok(req)
}
async fn from_res(res: http::Response<ResponseBody>) -> Result<Self, ServerFnError<CustErr>> {
async fn from_res(res: http::Response<ResponseBody>) -> Result<Self, ServerFnError<E>> {
let (_parts, body) = res.into_parts();
let body_bytes = body
.collect()
.await
.map(|c| c.to_bytes())
.map_err(|e| ServerFnError::Deserialization(e.to_string()))?;
.map_err(|e| ServerFnErrorErr::Deserialization(e.to_string()).into())?;
ciborium::de::from_reader(body_bytes.as_ref())
.map_err(|e| ServerFnError::Args(e.to_string()))
.map_err(|e| ServerFnErrorErr::Args(e.to_string()).into())
}
async fn into_res(self) -> http::Response<Body> {

View File

@@ -1,8 +1,8 @@
use super::{Encoding, FromReq, FromRes, Streaming};
use crate::{
error::{NoCustomError, ServerFnError},
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
request::{ClientReq, Req},
response::{ClientRes, Res},
response::{ClientRes, TryRes},
IntoReq, IntoRes,
};
use bytes::Bytes;
@@ -18,55 +18,58 @@ impl Encoding for Json {
const METHOD: Method = Method::POST;
}
impl<CustErr, T, Request> IntoReq<Json, Request, CustErr> for T
impl<E, T, Request> IntoReq<Json, Request, E> for T
where
Request: ClientReq<CustErr>,
Request: ClientReq<E>,
T: Serialize + Send,
E: FromServerFnError,
{
fn into_req(
self,
path: &str,
accepts: &str,
) -> Result<Request, ServerFnError<CustErr>> {
let data = serde_json::to_string(&self)
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
let data = serde_json::to_string(&self).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Request::try_new_post(path, accepts, Json::CONTENT_TYPE, data)
}
}
impl<CustErr, T, Request> FromReq<Json, Request, CustErr> for T
impl<E, T, Request> FromReq<Json, Request, E> for T
where
Request: Req<CustErr> + Send + 'static,
Request: Req<E> + Send + 'static,
T: DeserializeOwned,
E: FromServerFnError,
{
async fn from_req(req: Request) -> Result<Self, ServerFnError<CustErr>> {
async fn from_req(req: Request) -> Result<Self, E> {
let string_data = req.try_into_string().await?;
serde_json::from_str::<Self>(&string_data)
.map_err(|e| ServerFnError::Args(e.to_string()))
.map_err(|e| ServerFnErrorErr::Args(e.to_string()).into_app_error())
}
}
impl<CustErr, T, Response> IntoRes<Json, Response, CustErr> for T
impl<E, T, Response> IntoRes<Json, Response, E> for T
where
Response: Res<CustErr>,
Response: TryRes<E>,
T: Serialize + Send,
E: FromServerFnError,
{
async fn into_res(self) -> Result<Response, ServerFnError<CustErr>> {
let data = serde_json::to_string(&self)
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
async fn into_res(self) -> Result<Response, E> {
let data = serde_json::to_string(&self).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Response::try_from_string(Json::CONTENT_TYPE, data)
}
}
impl<CustErr, T, Response> FromRes<Json, Response, CustErr> for T
impl<E, T, Response> FromRes<Json, Response, E> for T
where
Response: ClientRes<CustErr> + Send,
Response: ClientRes<E> + Send,
T: DeserializeOwned + Send,
E: FromServerFnError,
{
async fn from_res(res: Response) -> Result<Self, ServerFnError<CustErr>> {
async fn from_res(res: Response) -> Result<Self, E> {
let data = res.try_into_string().await?;
serde_json::from_str(&data)
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
serde_json::from_str(&data).map_err(|e| {
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()
})
}
}
@@ -102,35 +105,31 @@ impl Encoding for StreamingJson {
/// end before the output will begin.
///
/// Streaming requests are only allowed over HTTP2 or HTTP3.
pub struct JsonStream<T, CustErr = NoCustomError>(
Pin<Box<dyn Stream<Item = Result<T, ServerFnError<CustErr>>> + Send>>,
);
pub struct JsonStream<T, E>(Pin<Box<dyn Stream<Item = Result<T, E>> + Send>>);
impl<T, CustErr> std::fmt::Debug for JsonStream<T, CustErr> {
impl<T, E> std::fmt::Debug for JsonStream<T, E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("JsonStream").finish()
}
}
impl<T> JsonStream<T> {
impl<T, E> JsonStream<T, E> {
/// Creates a new `ByteStream` from the given stream.
pub fn new(
value: impl Stream<Item = Result<T, ServerFnError>> + Send + 'static,
value: impl Stream<Item = Result<T, E>> + Send + 'static,
) -> Self {
Self(Box::pin(value.map(|value| value)))
}
}
impl<T, CustErr> JsonStream<T, CustErr> {
impl<T, E> JsonStream<T, E> {
/// Consumes the wrapper, returning a stream of text.
pub fn into_inner(
self,
) -> impl Stream<Item = Result<T, ServerFnError<CustErr>>> + Send {
pub fn into_inner(self) -> impl Stream<Item = Result<T, E>> + Send {
self.0
}
}
impl<S, T: 'static, CustErr: 'static> From<S> for JsonStream<T, CustErr>
impl<S, T: 'static, E: 'static> From<S> for JsonStream<T, E>
where
S: Stream<Item = T> + Send + 'static,
{
@@ -139,18 +138,15 @@ where
}
}
impl<CustErr, S, T, Request> IntoReq<StreamingJson, Request, CustErr> for S
impl<E, S, T, Request> IntoReq<StreamingJson, Request, E> for S
where
Request: ClientReq<CustErr>,
Request: ClientReq<E>,
S: Stream<Item = T> + Send + 'static,
T: Serialize + 'static,
E: FromServerFnError + Serialize,
{
fn into_req(
self,
path: &str,
accepts: &str,
) -> Result<Request, ServerFnError<CustErr>> {
let data: JsonStream<T> = self.into();
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
let data: JsonStream<T, E> = self.into();
Request::try_new_streaming(
path,
accepts,
@@ -164,56 +160,61 @@ where
}
}
impl<CustErr, T, S, Request> FromReq<StreamingJson, Request, CustErr> for S
impl<E, T, S, Request> FromReq<StreamingJson, Request, E> for S
where
Request: Req<CustErr> + Send + 'static,
Request: Req<E> + Send + 'static,
// The additional `Stream<Item = T>` bound is never used, but it is required to avoid an error where `T` is unconstrained
S: Stream<Item = T> + From<JsonStream<T>> + Send + 'static,
S: Stream<Item = T> + From<JsonStream<T, E>> + Send + 'static,
T: DeserializeOwned + 'static,
E: FromServerFnError,
{
async fn from_req(req: Request) -> Result<Self, ServerFnError<CustErr>> {
async fn from_req(req: Request) -> Result<Self, E> {
let data = req.try_into_stream()?;
let s = JsonStream::new(data.map(|chunk| {
chunk.and_then(|bytes| {
serde_json::from_slice(bytes.as_ref())
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
serde_json::from_slice(bytes.as_ref()).map_err(|e| {
ServerFnErrorErr::Deserialization(e.to_string())
.into_app_error()
})
})
}));
Ok(s.into())
}
}
impl<CustErr, T, Response> IntoRes<StreamingJson, Response, CustErr>
for JsonStream<T, CustErr>
impl<E, T, Response> IntoRes<StreamingJson, Response, E> for JsonStream<T, E>
where
Response: Res<CustErr>,
CustErr: 'static,
Response: TryRes<E>,
T: Serialize + 'static,
E: FromServerFnError,
{
async fn into_res(self) -> Result<Response, ServerFnError<CustErr>> {
async fn into_res(self) -> Result<Response, E> {
Response::try_from_stream(
Streaming::CONTENT_TYPE,
self.into_inner().map(|value| {
serde_json::to_vec(&value?)
.map(Bytes::from)
.map_err(|e| ServerFnError::Serialization(e.to_string()))
serde_json::to_vec(&value?).map(Bytes::from).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string())
.into_app_error()
})
}),
)
}
}
impl<CustErr, T, Response> FromRes<StreamingJson, Response, CustErr>
for JsonStream<T>
impl<E, T, Response> FromRes<StreamingJson, Response, E> for JsonStream<T, E>
where
Response: ClientRes<CustErr> + Send,
Response: ClientRes<E> + Send,
T: DeserializeOwned,
E: FromServerFnError,
{
async fn from_res(res: Response) -> Result<Self, ServerFnError<CustErr>> {
async fn from_res(res: Response) -> Result<Self, E> {
let stream = res.try_into_stream()?;
Ok(JsonStream::new(stream.map(|chunk| {
chunk.and_then(|bytes| {
serde_json::from_slice(bytes.as_ref())
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
serde_json::from_slice(bytes.as_ref()).map_err(|e| {
ServerFnErrorErr::Deserialization(e.to_string())
.into_app_error()
})
})
})))
}

View File

@@ -55,7 +55,6 @@ mod postcard;
pub use postcard::*;
mod stream;
use crate::error::ServerFnError;
use futures::Future;
use http::Method;
pub use stream::*;
@@ -71,31 +70,27 @@ pub use stream::*;
/// For example, heres the implementation for [`Json`].
///
/// ```rust,ignore
/// impl<CustErr, T, Request> IntoReq<Json, Request, CustErr> for T
/// impl<E, T, Request> IntoReq<Json, Request, E> for T
/// where
/// Request: ClientReq<CustErr>,
/// Request: ClientReq<E>,
/// T: Serialize + Send,
/// {
/// fn into_req(
/// self,
/// path: &str,
/// accepts: &str,
/// ) -> Result<Request, ServerFnError<CustErr>> {
/// ) -> Result<Request, E> {
/// // try to serialize the data
/// let data = serde_json::to_string(&self)
/// .map_err(|e| ServerFnError::Serialization(e.to_string()))?;
/// .map_err(|e| ServerFnErrorErr::Serialization(e.to_string()).into_app_error())?;
/// // and use it as the body of a POST request
/// Request::try_new_post(path, accepts, Json::CONTENT_TYPE, data)
/// }
/// }
/// ```
pub trait IntoReq<Encoding, Request, CustErr> {
pub trait IntoReq<Encoding, Request, E> {
/// Attempts to serialize the arguments into an HTTP request.
fn into_req(
self,
path: &str,
accepts: &str,
) -> Result<Request, ServerFnError<CustErr>>;
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E>;
}
/// Deserializes an HTTP request into the data type, on the server.
@@ -109,32 +104,31 @@ pub trait IntoReq<Encoding, Request, CustErr> {
/// For example, heres the implementation for [`Json`].
///
/// ```rust,ignore
/// impl<CustErr, T, Request> FromReq<Json, Request, CustErr> for T
/// impl<E, T, Request> FromReq<Json, Request, E> for T
/// where
/// // require the Request implement `Req`
/// Request: Req<CustErr> + Send + 'static,
/// Request: Req<E> + Send + 'static,
/// // require that the type can be deserialized with `serde`
/// T: DeserializeOwned,
/// E: FromServerFnError,
/// {
/// async fn from_req(
/// req: Request,
/// ) -> Result<Self, ServerFnError<CustErr>> {
/// ) -> Result<Self, E> {
/// // try to convert the body of the request into a `String`
/// let string_data = req.try_into_string().await?;
/// // deserialize the data
/// serde_json::from_str::<Self>(&string_data)
/// .map_err(|e| ServerFnError::Args(e.to_string()))
/// serde_json::from_str(&string_data)
/// .map_err(|e| ServerFnErrorErr::Args(e.to_string()).into_app_error())
/// }
/// }
/// ```
pub trait FromReq<Encoding, Request, CustErr>
pub trait FromReq<Encoding, Request, E>
where
Self: Sized,
{
/// Attempts to deserialize the arguments from a request.
fn from_req(
req: Request,
) -> impl Future<Output = Result<Self, ServerFnError<CustErr>>> + Send;
fn from_req(req: Request) -> impl Future<Output = Result<Self, E>> + Send;
}
/// Serializes the data type into an HTTP response.
@@ -148,25 +142,24 @@ where
/// For example, heres the implementation for [`Json`].
///
/// ```rust,ignore
/// impl<CustErr, T, Response> IntoRes<Json, Response, CustErr> for T
/// impl<E, T, Response> IntoRes<Json, Response, E> for T
/// where
/// Response: Res<CustErr>,
/// Response: Res<E>,
/// T: Serialize + Send,
/// E: FromServerFnError,
/// {
/// async fn into_res(self) -> Result<Response, ServerFnError<CustErr>> {
/// async fn into_res(self) -> Result<Response, E> {
/// // try to serialize the data
/// let data = serde_json::to_string(&self)
/// .map_err(|e| ServerFnError::Serialization(e.to_string()))?;
/// .map_err(|e| ServerFnErrorErr::Serialization(e.to_string()).into())?;
/// // and use it as the body of a response
/// Response::try_from_string(Json::CONTENT_TYPE, data)
/// }
/// }
/// ```
pub trait IntoRes<Encoding, Response, CustErr> {
pub trait IntoRes<Encoding, Response, E> {
/// Attempts to serialize the output into an HTTP response.
fn into_res(
self,
) -> impl Future<Output = Result<Response, ServerFnError<CustErr>>> + Send;
fn into_res(self) -> impl Future<Output = Result<Response, E>> + Send;
}
/// Deserializes the data type from an HTTP response.
@@ -181,30 +174,29 @@ pub trait IntoRes<Encoding, Response, CustErr> {
/// For example, heres the implementation for [`Json`].
///
/// ```rust,ignore
/// impl<CustErr, T, Response> FromRes<Json, Response, CustErr> for T
/// impl<E, T, Response> FromRes<Json, Response, E> for T
/// where
/// Response: ClientRes<CustErr> + Send,
/// Response: ClientRes<E> + Send,
/// T: DeserializeOwned + Send,
/// E: FromServerFnError,
/// {
/// async fn from_res(
/// res: Response,
/// ) -> Result<Self, ServerFnError<CustErr>> {
/// ) -> Result<Self, E> {
/// // extracts the request body
/// let data = res.try_into_string().await?;
/// // and tries to deserialize it as JSON
/// serde_json::from_str(&data)
/// .map_err(|e| ServerFnError::Deserialization(e.to_string()))
/// .map_err(|e| ServerFnErrorErr::Deserialization(e.to_string()).into_app_error())
/// }
/// }
/// ```
pub trait FromRes<Encoding, Response, CustErr>
pub trait FromRes<Encoding, Response, E>
where
Self: Sized,
{
/// Attempts to deserialize the outputs from a response.
fn from_res(
res: Response,
) -> impl Future<Output = Result<Self, ServerFnError<CustErr>>> + Send;
fn from_res(res: Response) -> impl Future<Output = Result<Self, E>> + Send;
}
/// Defines a particular encoding format, which can be used for serializing or deserializing data.

View File

@@ -1,8 +1,8 @@
use super::{Encoding, FromReq, FromRes, IntoReq, IntoRes};
use crate::{
error::ServerFnError,
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
request::{ClientReq, Req},
response::{ClientRes, Res},
response::{ClientRes, TryRes},
};
use bytes::Bytes;
use http::Method;
@@ -16,18 +16,16 @@ impl Encoding for MsgPack {
const METHOD: Method = Method::POST;
}
impl<T, Request, Err> IntoReq<MsgPack, Request, Err> for T
impl<T, Request, E> IntoReq<MsgPack, Request, E> for T
where
Request: ClientReq<Err>,
Request: ClientReq<E>,
T: Serialize,
E: FromServerFnError,
{
fn into_req(
self,
path: &str,
accepts: &str,
) -> Result<Request, ServerFnError<Err>> {
let data = rmp_serde::to_vec(&self)
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
let data = rmp_serde::to_vec(&self).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Request::try_new_post_bytes(
path,
MsgPack::CONTENT_TYPE,
@@ -37,38 +35,43 @@ where
}
}
impl<T, Request, Err> FromReq<MsgPack, Request, Err> for T
impl<T, Request, E> FromReq<MsgPack, Request, E> for T
where
Request: Req<Err> + Send,
Request: Req<E> + Send,
T: DeserializeOwned,
E: FromServerFnError,
{
async fn from_req(req: Request) -> Result<Self, ServerFnError<Err>> {
async fn from_req(req: Request) -> Result<Self, E> {
let data = req.try_into_bytes().await?;
rmp_serde::from_slice::<T>(&data)
.map_err(|e| ServerFnError::Args(e.to_string()))
.map_err(|e| ServerFnErrorErr::Args(e.to_string()).into_app_error())
}
}
impl<T, Response, Err> IntoRes<MsgPack, Response, Err> for T
impl<T, Response, E> IntoRes<MsgPack, Response, E> for T
where
Response: Res<Err>,
Response: TryRes<E>,
T: Serialize + Send,
E: FromServerFnError,
{
async fn into_res(self) -> Result<Response, ServerFnError<Err>> {
let data = rmp_serde::to_vec(&self)
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
async fn into_res(self) -> Result<Response, E> {
let data = rmp_serde::to_vec(&self).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Response::try_from_bytes(MsgPack::CONTENT_TYPE, Bytes::from(data))
}
}
impl<T, Response, Err> FromRes<MsgPack, Response, Err> for T
impl<T, Response, E> FromRes<MsgPack, Response, E> for T
where
Response: ClientRes<Err> + Send,
Response: ClientRes<E> + Send,
T: DeserializeOwned,
E: FromServerFnError,
{
async fn from_res(res: Response) -> Result<Self, ServerFnError<Err>> {
async fn from_res(res: Response) -> Result<Self, E> {
let data = res.try_into_bytes().await?;
rmp_serde::from_slice(&data)
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
rmp_serde::from_slice(&data).map_err(|e| {
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()
})
}
}

View File

@@ -1,6 +1,6 @@
use super::{Encoding, FromReq};
use crate::{
error::ServerFnError,
error::FromServerFnError,
request::{browser::BrowserFormData, ClientReq, Req},
IntoReq,
};
@@ -56,16 +56,12 @@ impl From<FormData> for MultipartData {
}
}
impl<CustErr, T, Request> IntoReq<MultipartFormData, Request, CustErr> for T
impl<E, T, Request> IntoReq<MultipartFormData, Request, E> for T
where
Request: ClientReq<CustErr, FormData = BrowserFormData>,
Request: ClientReq<E, FormData = BrowserFormData>,
T: Into<MultipartData>,
{
fn into_req(
self,
path: &str,
accepts: &str,
) -> Result<Request, ServerFnError<CustErr>> {
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
let multi = self.into();
Request::try_new_multipart(
path,
@@ -75,20 +71,20 @@ where
}
}
impl<CustErr, T, Request> FromReq<MultipartFormData, Request, CustErr> for T
impl<E, T, Request> FromReq<MultipartFormData, Request, E> for T
where
Request: Req<CustErr> + Send + 'static,
Request: Req<E> + Send + 'static,
T: From<MultipartData>,
CustErr: 'static,
E: FromServerFnError,
{
async fn from_req(req: Request) -> Result<Self, ServerFnError<CustErr>> {
async fn from_req(req: Request) -> Result<Self, E> {
let boundary = req
.to_content_type()
.and_then(|ct| multer::parse_boundary(ct).ok())
.expect("couldn't parse boundary");
let stream = req.try_into_stream()?;
let data = multer::Multipart::new(
stream.map(|data| data.map_err(|e| e.to_string())),
stream.map(|data| data.map_err(|e| e.ser())),
boundary,
);
Ok(MultipartData::Server(data).into())

View File

@@ -1,8 +1,8 @@
use super::{Encoding, FromReq, FromRes, IntoReq, IntoRes};
use crate::{
error::ServerFnError,
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
request::{ClientReq, Req},
response::{ClientRes, Res},
response::{ClientRes, TryRes},
};
use bytes::Bytes;
use http::Method;
@@ -16,18 +16,16 @@ impl Encoding for Postcard {
const METHOD: Method = Method::POST;
}
impl<T, Request, Err> IntoReq<Postcard, Request, Err> for T
impl<T, Request, E> IntoReq<Postcard, Request, E> for T
where
Request: ClientReq<Err>,
Request: ClientReq<E>,
T: Serialize,
E: FromServerFnError,
{
fn into_req(
self,
path: &str,
accepts: &str,
) -> Result<Request, ServerFnError<Err>> {
let data = postcard::to_allocvec(&self)
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
let data = postcard::to_allocvec(&self).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Request::try_new_post_bytes(
path,
Postcard::CONTENT_TYPE,
@@ -37,38 +35,43 @@ where
}
}
impl<T, Request, Err> FromReq<Postcard, Request, Err> for T
impl<T, Request, E> FromReq<Postcard, Request, E> for T
where
Request: Req<Err> + Send,
Request: Req<E> + Send,
T: DeserializeOwned,
E: FromServerFnError,
{
async fn from_req(req: Request) -> Result<Self, ServerFnError<Err>> {
async fn from_req(req: Request) -> Result<Self, E> {
let data = req.try_into_bytes().await?;
postcard::from_bytes::<T>(&data)
.map_err(|e| ServerFnError::Args(e.to_string()))
.map_err(|e| ServerFnErrorErr::Args(e.to_string()).into_app_error())
}
}
impl<T, Response, Err> IntoRes<Postcard, Response, Err> for T
impl<T, Response, E> IntoRes<Postcard, Response, E> for T
where
Response: Res<Err>,
Response: TryRes<E>,
T: Serialize + Send,
E: FromServerFnError,
{
async fn into_res(self) -> Result<Response, ServerFnError<Err>> {
let data = postcard::to_allocvec(&self)
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
async fn into_res(self) -> Result<Response, E> {
let data = postcard::to_allocvec(&self).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Response::try_from_bytes(Postcard::CONTENT_TYPE, Bytes::from(data))
}
}
impl<T, Response, Err> FromRes<Postcard, Response, Err> for T
impl<T, Response, E> FromRes<Postcard, Response, E> for T
where
Response: ClientRes<Err> + Send,
Response: ClientRes<E> + Send,
T: DeserializeOwned,
E: FromServerFnError,
{
async fn from_res(res: Response) -> Result<Self, ServerFnError<Err>> {
async fn from_res(res: Response) -> Result<Self, E> {
let data = res.try_into_bytes().await?;
postcard::from_bytes(&data)
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
postcard::from_bytes(&data).map_err(|e| {
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()
})
}
}

View File

@@ -1,8 +1,8 @@
use super::{Encoding, FromReq, FromRes, IntoReq, IntoRes};
use crate::{
error::ServerFnError,
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
request::{ClientReq, Req},
response::{ClientRes, Res},
response::{ClientRes, TryRes},
};
use bytes::Bytes;
use futures::StreamExt;
@@ -29,39 +29,38 @@ impl Encoding for Rkyv {
const METHOD: Method = Method::POST;
}
impl<CustErr, T, Request> IntoReq<Rkyv, Request, CustErr> for T
impl<E, T, Request> IntoReq<Rkyv, Request, E> for T
where
Request: ClientReq<CustErr>,
Request: ClientReq<E>,
T: Archive + for<'a> Serialize<RkyvSerializer<'a>>,
T::Archived: Deserialize<T, RkyvDeserializer>
+ for<'a> CheckBytes<RkyvValidator<'a>>,
E: FromServerFnError,
{
fn into_req(
self,
path: &str,
accepts: &str,
) -> Result<Request, ServerFnError<CustErr>> {
let encoded = rkyv::to_bytes::<rancor::Error>(&self)
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
let encoded = rkyv::to_bytes::<rancor::Error>(&self).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
let bytes = Bytes::copy_from_slice(encoded.as_ref());
Request::try_new_post_bytes(path, accepts, Rkyv::CONTENT_TYPE, bytes)
}
}
impl<CustErr, T, Request> FromReq<Rkyv, Request, CustErr> for T
impl<E, T, Request> FromReq<Rkyv, Request, E> for T
where
Request: Req<CustErr> + Send + 'static,
Request: Req<E> + Send + 'static,
T: Archive + for<'a> Serialize<RkyvSerializer<'a>>,
T::Archived: Deserialize<T, RkyvDeserializer>
+ for<'a> CheckBytes<RkyvValidator<'a>>,
E: FromServerFnError,
{
async fn from_req(req: Request) -> Result<Self, ServerFnError<CustErr>> {
async fn from_req(req: Request) -> Result<Self, E> {
let mut aligned = AlignedVec::<1024>::new();
let mut body_stream = Box::pin(req.try_into_stream()?);
while let Some(chunk) = body_stream.next().await {
match chunk {
Err(e) => {
return Err(ServerFnError::Deserialization(e.to_string()))
return Err(e);
}
Ok(bytes) => {
for byte in bytes {
@@ -71,36 +70,40 @@ where
}
}
rkyv::from_bytes::<T, rancor::Error>(aligned.as_ref())
.map_err(|e| ServerFnError::Args(e.to_string()))
.map_err(|e| ServerFnErrorErr::Args(e.to_string()).into_app_error())
}
}
impl<CustErr, T, Response> IntoRes<Rkyv, Response, CustErr> for T
impl<E, T, Response> IntoRes<Rkyv, Response, E> for T
where
Response: Res<CustErr>,
Response: TryRes<E>,
T: Send,
T: Archive + for<'a> Serialize<RkyvSerializer<'a>>,
T::Archived: Deserialize<T, RkyvDeserializer>
+ for<'a> CheckBytes<RkyvValidator<'a>>,
E: FromServerFnError,
{
async fn into_res(self) -> Result<Response, ServerFnError<CustErr>> {
let encoded = rkyv::to_bytes::<rancor::Error>(&self)
.map_err(|e| ServerFnError::Serialization(format!("{e:?}")))?;
async fn into_res(self) -> Result<Response, E> {
let encoded = rkyv::to_bytes::<rancor::Error>(&self).map_err(|e| {
ServerFnErrorErr::Serialization(format!("{e:?}")).into_app_error()
})?;
let bytes = Bytes::copy_from_slice(encoded.as_ref());
Response::try_from_bytes(Rkyv::CONTENT_TYPE, bytes)
}
}
impl<CustErr, T, Response> FromRes<Rkyv, Response, CustErr> for T
impl<E, T, Response> FromRes<Rkyv, Response, E> for T
where
Response: ClientRes<CustErr> + Send,
Response: ClientRes<E> + Send,
T: Archive + for<'a> Serialize<RkyvSerializer<'a>>,
T::Archived: Deserialize<T, RkyvDeserializer>
+ for<'a> CheckBytes<RkyvValidator<'a>>,
E: FromServerFnError,
{
async fn from_res(res: Response) -> Result<Self, ServerFnError<CustErr>> {
async fn from_res(res: Response) -> Result<Self, E> {
let data = res.try_into_bytes().await?;
rkyv::from_bytes::<T, rancor::Error>(&data)
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
rkyv::from_bytes::<T, rancor::Error>(&data).map_err(|e| {
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()
})
}
}

View File

@@ -1,8 +1,8 @@
use super::{Encoding, FromReq, FromRes};
use crate::{
error::ServerFnError,
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
request::{ClientReq, Req},
response::{ClientRes, Res},
response::{ClientRes, TryRes},
IntoReq, IntoRes,
};
use http::Method;
@@ -15,68 +15,68 @@ impl Encoding for SerdeLite {
const METHOD: Method = Method::POST;
}
impl<CustErr, T, Request> IntoReq<SerdeLite, Request, CustErr> for T
impl<E, T, Request> IntoReq<SerdeLite, Request, E> for T
where
Request: ClientReq<CustErr>,
Request: ClientReq<E>,
T: Serialize + Send,
E: FromServerFnError,
{
fn into_req(
self,
path: &str,
accepts: &str,
) -> Result<Request, ServerFnError<CustErr>> {
let data = serde_json::to_string(
&self
.serialize()
.map_err(|e| ServerFnError::Serialization(e.to_string()))?,
)
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
let data = serde_json::to_string(&self.serialize().map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?)
.map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Request::try_new_post(path, accepts, SerdeLite::CONTENT_TYPE, data)
}
}
impl<CustErr, T, Request> FromReq<SerdeLite, Request, CustErr> for T
impl<E, T, Request> FromReq<SerdeLite, Request, E> for T
where
Request: Req<CustErr> + Send + 'static,
Request: Req<E> + Send + 'static,
T: Deserialize,
E: FromServerFnError,
{
async fn from_req(req: Request) -> Result<Self, ServerFnError<CustErr>> {
async fn from_req(req: Request) -> Result<Self, E> {
let string_data = req.try_into_string().await?;
Self::deserialize(
&serde_json::from_str(&string_data)
.map_err(|e| ServerFnError::Args(e.to_string()))?,
)
.map_err(|e| ServerFnError::Args(e.to_string()))
Self::deserialize(&serde_json::from_str(&string_data).map_err(|e| {
ServerFnErrorErr::Args(e.to_string()).into_app_error()
})?)
.map_err(|e| ServerFnErrorErr::Args(e.to_string()).into_app_error())
}
}
impl<CustErr, T, Response> IntoRes<SerdeLite, Response, CustErr> for T
impl<E, T, Response> IntoRes<SerdeLite, Response, E> for T
where
Response: Res<CustErr>,
Response: TryRes<E>,
T: Serialize + Send,
E: FromServerFnError,
{
async fn into_res(self) -> Result<Response, ServerFnError<CustErr>> {
let data = serde_json::to_string(
&self
.serialize()
.map_err(|e| ServerFnError::Serialization(e.to_string()))?,
)
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
async fn into_res(self) -> Result<Response, E> {
let data = serde_json::to_string(&self.serialize().map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?)
.map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Response::try_from_string(SerdeLite::CONTENT_TYPE, data)
}
}
impl<CustErr, T, Response> FromRes<SerdeLite, Response, CustErr> for T
impl<E, T, Response> FromRes<SerdeLite, Response, E> for T
where
Response: ClientRes<CustErr> + Send,
Response: ClientRes<E> + Send,
T: Deserialize + Send,
E: FromServerFnError,
{
async fn from_res(res: Response) -> Result<Self, ServerFnError<CustErr>> {
async fn from_res(res: Response) -> Result<Self, E> {
let data = res.try_into_string().await?;
Self::deserialize(
&serde_json::from_str(&data)
.map_err(|e| ServerFnError::Args(e.to_string()))?,
)
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
Self::deserialize(&serde_json::from_str(&data).map_err(|e| {
ServerFnErrorErr::Args(e.to_string()).into_app_error()
})?)
.map_err(|e| {
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()
})
}
}

View File

@@ -1,9 +1,9 @@
use super::{Encoding, FromReq, FromRes, IntoReq};
use crate::{
error::{NoCustomError, ServerFnError},
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
request::{ClientReq, Req},
response::{ClientRes, Res},
IntoRes,
response::{ClientRes, TryRes},
IntoRes, ServerFnError,
};
use bytes::Bytes;
use futures::{Stream, StreamExt};
@@ -29,26 +29,22 @@ impl Encoding for Streaming {
const METHOD: Method = Method::POST;
}
impl<CustErr, T, Request> IntoReq<Streaming, Request, CustErr> for T
impl<E, T, Request> IntoReq<Streaming, Request, E> for T
where
Request: ClientReq<CustErr>,
Request: ClientReq<E>,
T: Stream<Item = Bytes> + Send + Sync + 'static,
{
fn into_req(
self,
path: &str,
accepts: &str,
) -> Result<Request, ServerFnError<CustErr>> {
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
Request::try_new_streaming(path, accepts, Streaming::CONTENT_TYPE, self)
}
}
impl<CustErr, T, Request> FromReq<Streaming, Request, CustErr> for T
impl<E, T, Request> FromReq<Streaming, Request, E> for T
where
Request: Req<CustErr> + Send + 'static,
T: From<ByteStream> + 'static,
Request: Req<E> + Send + 'static,
T: From<ByteStream<E>> + 'static,
{
async fn from_req(req: Request) -> Result<Self, ServerFnError<CustErr>> {
async fn from_req(req: Request) -> Result<Self, E> {
let data = req.try_into_stream()?;
let s = ByteStream::new(data);
Ok(s.into())
@@ -67,29 +63,25 @@ where
/// end before the output will begin.
///
/// Streaming requests are only allowed over HTTP2 or HTTP3.
pub struct ByteStream<CustErr = NoCustomError>(
Pin<Box<dyn Stream<Item = Result<Bytes, ServerFnError<CustErr>>> + Send>>,
);
pub struct ByteStream<E>(Pin<Box<dyn Stream<Item = Result<Bytes, E>> + Send>>);
impl<CustErr> ByteStream<CustErr> {
impl<E> ByteStream<E> {
/// Consumes the wrapper, returning a stream of bytes.
pub fn into_inner(
self,
) -> impl Stream<Item = Result<Bytes, ServerFnError<CustErr>>> + Send {
pub fn into_inner(self) -> impl Stream<Item = Result<Bytes, E>> + Send {
self.0
}
}
impl<CustErr> Debug for ByteStream<CustErr> {
impl<E> Debug for ByteStream<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("ByteStream").finish()
}
}
impl ByteStream {
impl<E> ByteStream<E> {
/// Creates a new `ByteStream` from the given stream.
pub fn new<T>(
value: impl Stream<Item = Result<T, ServerFnError>> + Send + 'static,
value: impl Stream<Item = Result<T, E>> + Send + 'static,
) -> Self
where
T: Into<Bytes>,
@@ -98,7 +90,7 @@ impl ByteStream {
}
}
impl<S, T> From<S> for ByteStream
impl<E, S, T> From<S> for ByteStream<E>
where
S: Stream<Item = T> + Send + 'static,
T: Into<Bytes>,
@@ -108,22 +100,21 @@ where
}
}
impl<CustErr, Response> IntoRes<Streaming, Response, CustErr>
for ByteStream<CustErr>
impl<E, Response> IntoRes<Streaming, Response, E> for ByteStream<E>
where
Response: Res<CustErr>,
CustErr: 'static,
Response: TryRes<E>,
E: 'static,
{
async fn into_res(self) -> Result<Response, ServerFnError<CustErr>> {
async fn into_res(self) -> Result<Response, E> {
Response::try_from_stream(Streaming::CONTENT_TYPE, self.into_inner())
}
}
impl<CustErr, Response> FromRes<Streaming, Response, CustErr> for ByteStream
impl<E, Response> FromRes<Streaming, Response, E> for ByteStream<E>
where
Response: ClientRes<CustErr> + Send,
Response: ClientRes<E> + Send,
{
async fn from_res(res: Response) -> Result<Self, ServerFnError<CustErr>> {
async fn from_res(res: Response) -> Result<Self, E> {
let stream = res.try_into_stream()?;
Ok(ByteStream(Box::pin(stream)))
}
@@ -160,35 +151,33 @@ impl Encoding for StreamingText {
/// end before the output will begin.
///
/// Streaming requests are only allowed over HTTP2 or HTTP3.
pub struct TextStream<CustErr = NoCustomError>(
Pin<Box<dyn Stream<Item = Result<String, ServerFnError<CustErr>>> + Send>>,
pub struct TextStream<E = ServerFnError>(
Pin<Box<dyn Stream<Item = Result<String, E>> + Send>>,
);
impl<CustErr> Debug for TextStream<CustErr> {
impl<E> Debug for TextStream<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("TextStream").finish()
}
}
impl TextStream {
impl<E> TextStream<E> {
/// Creates a new `ByteStream` from the given stream.
pub fn new(
value: impl Stream<Item = Result<String, ServerFnError>> + Send + 'static,
value: impl Stream<Item = Result<String, E>> + Send + 'static,
) -> Self {
Self(Box::pin(value.map(|value| value)))
}
}
impl<CustErr> TextStream<CustErr> {
impl<E> TextStream<E> {
/// Consumes the wrapper, returning a stream of text.
pub fn into_inner(
self,
) -> impl Stream<Item = Result<String, ServerFnError<CustErr>>> + Send {
pub fn into_inner(self) -> impl Stream<Item = Result<String, E>> + Send {
self.0
}
}
impl<S, T> From<S> for TextStream
impl<E, S, T> From<S> for TextStream<E>
where
S: Stream<Item = T> + Send + 'static,
T: Into<String>,
@@ -198,16 +187,13 @@ where
}
}
impl<CustErr, T, Request> IntoReq<StreamingText, Request, CustErr> for T
impl<E, T, Request> IntoReq<StreamingText, Request, E> for T
where
Request: ClientReq<CustErr>,
T: Into<TextStream>,
Request: ClientReq<E>,
T: Into<TextStream<E>>,
E: 'static,
{
fn into_req(
self,
path: &str,
accepts: &str,
) -> Result<Request, ServerFnError<CustErr>> {
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
let data = self.into();
Request::try_new_streaming(
path,
@@ -218,30 +204,32 @@ where
}
}
impl<CustErr, T, Request> FromReq<StreamingText, Request, CustErr> for T
impl<E, T, Request> FromReq<StreamingText, Request, E> for T
where
Request: Req<CustErr> + Send + 'static,
T: From<TextStream> + 'static,
Request: Req<E> + Send + 'static,
T: From<TextStream<E>> + 'static,
E: FromServerFnError,
{
async fn from_req(req: Request) -> Result<Self, ServerFnError<CustErr>> {
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| ServerFnError::Deserialization(e.to_string()))
String::from_utf8(bytes.to_vec()).map_err(|e| {
ServerFnErrorErr::Deserialization(e.to_string())
.into_app_error()
})
})
}));
Ok(s.into())
}
}
impl<CustErr, Response> IntoRes<StreamingText, Response, CustErr>
for TextStream<CustErr>
impl<E, Response> IntoRes<StreamingText, Response, E> for TextStream<E>
where
Response: Res<CustErr>,
CustErr: 'static,
Response: TryRes<E>,
E: 'static,
{
async fn into_res(self) -> Result<Response, ServerFnError<CustErr>> {
async fn into_res(self) -> Result<Response, E> {
Response::try_from_stream(
Streaming::CONTENT_TYPE,
self.into_inner().map(|stream| stream.map(Into::into)),
@@ -249,16 +237,19 @@ where
}
}
impl<CustErr, Response> FromRes<StreamingText, Response, CustErr> for TextStream
impl<E, Response> FromRes<StreamingText, Response, E> for TextStream<E>
where
Response: ClientRes<CustErr> + Send,
Response: ClientRes<E> + Send,
E: FromServerFnError,
{
async fn from_res(res: Response) -> Result<Self, ServerFnError<CustErr>> {
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| ServerFnError::Deserialization(e.to_string()))
String::from_utf8(bytes.into()).map_err(|e| {
ServerFnErrorErr::Deserialization(e.to_string())
.into_app_error()
})
})
}))))
}

View File

@@ -1,6 +1,6 @@
use super::{Encoding, FromReq, IntoReq};
use crate::{
error::ServerFnError,
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
request::{ClientReq, Req},
};
use http::Method;
@@ -17,32 +17,33 @@ impl Encoding for GetUrl {
const METHOD: Method = Method::GET;
}
impl<CustErr, T, Request> IntoReq<GetUrl, Request, CustErr> for T
impl<E, T, Request> IntoReq<GetUrl, Request, E> for T
where
Request: ClientReq<CustErr>,
Request: ClientReq<E>,
T: Serialize + Send,
E: FromServerFnError,
{
fn into_req(
self,
path: &str,
accepts: &str,
) -> Result<Request, ServerFnError<CustErr>> {
let data = serde_qs::to_string(&self)
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
let data = serde_qs::to_string(&self).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Request::try_new_get(path, accepts, GetUrl::CONTENT_TYPE, &data)
}
}
impl<CustErr, T, Request> FromReq<GetUrl, Request, CustErr> for T
impl<E, T, Request> FromReq<GetUrl, Request, E> for T
where
Request: Req<CustErr> + Send + 'static,
Request: Req<E> + Send + 'static,
T: DeserializeOwned,
E: FromServerFnError,
{
async fn from_req(req: Request) -> Result<Self, ServerFnError<CustErr>> {
async fn from_req(req: Request) -> Result<Self, E> {
let string_data = req.as_query().unwrap_or_default();
let args = serde_qs::Config::new(5, false)
.deserialize_str::<Self>(string_data)
.map_err(|e| ServerFnError::Args(e.to_string()))?;
.map_err(|e| {
ServerFnErrorErr::Args(e.to_string()).into_app_error()
})?;
Ok(args)
}
}
@@ -52,32 +53,33 @@ impl Encoding for PostUrl {
const METHOD: Method = Method::POST;
}
impl<CustErr, T, Request> IntoReq<PostUrl, Request, CustErr> for T
impl<E, T, Request> IntoReq<PostUrl, Request, E> for T
where
Request: ClientReq<CustErr>,
Request: ClientReq<E>,
T: Serialize + Send,
E: FromServerFnError,
{
fn into_req(
self,
path: &str,
accepts: &str,
) -> Result<Request, ServerFnError<CustErr>> {
let qs = serde_qs::to_string(&self)
.map_err(|e| ServerFnError::Serialization(e.to_string()))?;
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
let qs = serde_qs::to_string(&self).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Request::try_new_post(path, accepts, PostUrl::CONTENT_TYPE, qs)
}
}
impl<CustErr, T, Request> FromReq<PostUrl, Request, CustErr> for T
impl<E, T, Request> FromReq<PostUrl, Request, E> for T
where
Request: Req<CustErr> + Send + 'static,
Request: Req<E> + Send + 'static,
T: DeserializeOwned,
E: FromServerFnError,
{
async fn from_req(req: Request) -> Result<Self, ServerFnError<CustErr>> {
async fn from_req(req: Request) -> Result<Self, E> {
let string_data = req.try_into_string().await?;
let args = serde_qs::Config::new(5, false)
.deserialize_str::<Self>(&string_data)
.map_err(|e| ServerFnError::Args(e.to_string()))?;
.map_err(|e| {
ServerFnErrorErr::Args(e.to_string()).into_app_error()
})?;
Ok(args)
}
}
@@ -86,18 +88,18 @@ where
impl<T, Request, Response> Codec<Request, Response, GetUrlJson> for T
where
T: DeserializeOwned + Serialize + Send,
Request: Req<CustErr> + Send,
Response: Res<CustErr> + Send,
Request: Req<E> + Send,
Response: Res<E> + Send,
{
async fn from_req(req: Request) -> Result<Self, ServerFnError<CustErr>> {
async fn from_req(req: Request) -> Result<Self, ServerFnError<E>> {
let string_data = req.try_into_string()?;
let args = serde_json::from_str::<Self>(&string_data)
.map_err(|e| ServerFnError::Args(e.to_string()))?;
.map_err(|e| ServerFnErrorErr::Args(e.to_string()).into())?;
Ok(args)
}
async fn into_req(self) -> Result<Request, ServerFnError<CustErr>> {
async fn into_req(self) -> Result<Request, ServerFnError<E>> {
/* let qs = serde_qs::to_string(&self)?;
let req = http::Request::builder()
.method("GET")
@@ -110,7 +112,7 @@ where
todo!()
}
async fn from_res(res: Response) -> Result<Self, ServerFnError<CustErr>> {
async fn from_res(res: Response) -> Result<Self, ServerFnError<E>> {
todo!()
/* let (_parts, body) = res.into_parts();
@@ -118,7 +120,7 @@ where
.collect()
.await
.map(|c| c.to_bytes())
.map_err(|e| ServerFnError::Deserialization(e.to_string()))?;
.map_err(|e| ServerFnErrorErr::Deserialization(e.to_string()).into())?;
let string_data = String::from_utf8(body_bytes.to_vec())?;
serde_json::from_str(&string_data)
.map_err(|e| ServerFnError::Deserialization(e.to_string())) */

View File

@@ -1,7 +1,9 @@
use serde::{Deserialize, Serialize};
#![allow(deprecated)]
use base64::{engine::general_purpose::URL_SAFE, Engine as _};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{
fmt,
fmt::{Display, Write},
fmt::{self, Display, Write},
str::FromStr,
};
use thiserror::Error;
@@ -13,7 +15,7 @@ pub const SERVER_FN_ERROR_HEADER: &str = "serverfnerror";
impl From<ServerFnError> for Error {
fn from(e: ServerFnError) -> Self {
Error::from(ServerFnErrorErr::from(e))
Error::from(ServerFnErrorWrapper(e))
}
}
@@ -35,6 +37,11 @@ impl From<ServerFnError> for Error {
feature = "rkyv",
derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
#[deprecated(
since = "0.8.0",
note = "Now server_fn can return any error type other than ServerFnError, \
so the WrappedServerError variant will be removed in 0.9.0"
)]
pub struct NoCustomError;
// Implement `Display` for `NoCustomError`
@@ -55,11 +62,21 @@ impl FromStr for NoCustomError {
/// Wraps some error type, which may implement any of [`Error`](trait@std::error::Error), [`Clone`], or
/// [`Display`].
#[derive(Debug)]
#[deprecated(
since = "0.8.0",
note = "Now server_fn can return any error type other than ServerFnError, \
so the WrappedServerError variant will be removed in 0.9.0"
)]
pub struct WrapError<T>(pub T);
/// A helper macro to convert a variety of different types into `ServerFnError`.
/// This should mostly be used if you are implementing `From<ServerFnError>` for `YourError`.
#[macro_export]
#[deprecated(
since = "0.8.0",
note = "Now server_fn can return any error type other than ServerFnError, \
so the WrappedServerError variant will be removed in 0.9.0"
)]
macro_rules! server_fn_error {
() => {{
use $crate::{ViaError, WrapError};
@@ -75,6 +92,12 @@ macro_rules! server_fn_error {
/// This trait serves as the conversion method between a variety of types
/// and [`ServerFnError`].
#[deprecated(
since = "0.8.0",
note = "Now server_fn can return any error type other than ServerFnError, \
so users should place their custom error type instead of \
ServerFnError"
)]
pub trait ViaError<E> {
/// Converts something into an error.
fn to_server_error(&self) -> ServerFnError<E>;
@@ -90,6 +113,7 @@ impl<E: ServerFnErrorKind + std::error::Error + Clone> ViaError<E>
}
// A type tag for ServerFnError so we can special case it
#[deprecated]
pub(crate) trait ServerFnErrorKind {}
impl ServerFnErrorKind for ServerFnError {}
@@ -131,7 +155,8 @@ impl<E> ViaError<E> for WrapError<E> {
}
}
/// Type for errors that can occur when using server functions.
/// A type that can be used as the return type of the server function for easy error conversion with `?` operator.
/// This type can be replaced with any other error type that implements `FromServerFnError`.
///
/// Unlike [`ServerFnErrorErr`], this does not implement [`Error`](trait@std::error::Error).
/// This means that other error types can easily be converted into it using the
@@ -142,6 +167,12 @@ impl<E> ViaError<E> for WrapError<E> {
derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
pub enum ServerFnError<E = NoCustomError> {
#[deprecated(
since = "0.8.0",
note = "Now server_fn can return any error type other than \
ServerFnError, so users should place their custom error type \
instead of ServerFnError"
)]
/// A user-defined custom error type, which defaults to [`NoCustomError`].
WrappedServerError(E),
/// Error while trying to register the server function (only occurs in case of poisoned RwLock).
@@ -152,6 +183,8 @@ pub enum ServerFnError<E = NoCustomError> {
Response(String),
/// Occurs when there is an error while actually running the function on the server.
ServerError(String),
/// Occurs when there is an error while actually running the middleware on the server.
MiddlewareError(String),
/// Occurs on the client if there is an error deserializing the server's response.
Deserialization(String),
/// Occurs on the client if there is an error serializing the server function arguments.
@@ -198,6 +231,8 @@ where
),
ServerFnError::ServerError(s) =>
format!("error running server function: {s}"),
ServerFnError::MiddlewareError(s) =>
format!("error running middleware: {s}"),
ServerFnError::Deserialization(s) =>
format!("error deserializing server function results: {s}"),
ServerFnError::Serialization(s) =>
@@ -214,30 +249,45 @@ where
}
}
/// A serializable custom server function error type.
///
/// This is implemented for all types that implement [`FromStr`] + [`Display`].
///
/// This means you do not necessarily need the overhead of `serde` for a custom error type.
/// Instead, you can use something like `strum` to derive `FromStr` and `Display` for your
/// custom error type.
///
/// This is implemented for the default [`ServerFnError`], which uses [`NoCustomError`].
pub trait ServerFnErrorSerde: Sized {
/// Converts the custom error type to a [`String`].
fn ser(&self) -> Result<String, std::fmt::Error>;
/// Deserializes the custom error type from a [`String`].
fn de(data: &str) -> Self;
}
impl<CustErr> ServerFnErrorSerde for ServerFnError<CustErr>
impl<CustErr> FromServerFnError for ServerFnError<CustErr>
where
CustErr: FromStr + Display,
CustErr: std::fmt::Debug
+ Display
+ Serialize
+ DeserializeOwned
+ 'static
+ FromStr
+ Display,
{
fn ser(&self) -> Result<String, std::fmt::Error> {
fn from_server_fn_error(value: ServerFnErrorErr) -> Self {
match value {
ServerFnErrorErr::Registration(value) => {
ServerFnError::Registration(value)
}
ServerFnErrorErr::Request(value) => ServerFnError::Request(value),
ServerFnErrorErr::ServerError(value) => {
ServerFnError::ServerError(value)
}
ServerFnErrorErr::MiddlewareError(value) => {
ServerFnError::MiddlewareError(value)
}
ServerFnErrorErr::Deserialization(value) => {
ServerFnError::Deserialization(value)
}
ServerFnErrorErr::Serialization(value) => {
ServerFnError::Serialization(value)
}
ServerFnErrorErr::Args(value) => ServerFnError::Args(value),
ServerFnErrorErr::MissingArg(value) => {
ServerFnError::MissingArg(value)
}
ServerFnErrorErr::Response(value) => ServerFnError::Response(value),
}
}
fn ser(&self) -> String {
let mut buf = String::new();
match self {
let result = match self {
ServerFnError::WrappedServerError(e) => {
write!(&mut buf, "WrappedServerFn|{e}")
}
@@ -249,6 +299,9 @@ where
ServerFnError::ServerError(e) => {
write!(&mut buf, "ServerError|{e}")
}
ServerFnError::MiddlewareError(e) => {
write!(&mut buf, "MiddlewareError|{e}")
}
ServerFnError::Deserialization(e) => {
write!(&mut buf, "Deserialization|{e}")
}
@@ -259,8 +312,11 @@ where
ServerFnError::MissingArg(e) => {
write!(&mut buf, "MissingArg|{e}")
}
}?;
Ok(buf)
};
match result {
Ok(()) => buf,
Err(_) => "Serialization|".to_string(),
}
}
fn de(data: &str) -> Self {
@@ -311,20 +367,13 @@ where
}
}
/// Type for errors that can occur when using server functions.
///
/// Unlike [`ServerFnError`], this implements [`std::error::Error`]. This means
/// it can be used in situations in which the `Error` trait is required, but its
/// not possible to create a blanket implementation that converts other errors into
/// this type.
///
/// [`ServerFnError`] and [`ServerFnErrorErr`] mutually implement [`From`], so
/// it is easy to convert between the two types.
#[derive(Error, Debug, Clone, PartialEq, Eq)]
pub enum ServerFnErrorErr<E = NoCustomError> {
/// A user-defined custom error type, which defaults to [`NoCustomError`].
#[error("internal error: {0}")]
WrappedServerError(E),
/// Type for errors that can occur when using server functions. If you need to return a custom error type from a server function, implement `FromServerFnError` for your custom error type.
#[derive(Error, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
)]
pub enum ServerFnErrorErr {
/// Error while trying to register the server function (only occurs in case of poisoned RwLock).
#[error("error while trying to register the server function: {0}")]
Registration(String),
@@ -334,6 +383,9 @@ pub enum ServerFnErrorErr<E = NoCustomError> {
/// Occurs when there is an error while actually running the function on the server.
#[error("error running server function: {0}")]
ServerError(String),
/// Occurs when there is an error while actually running the middleware on the server.
#[error("error running middleware: {0}")]
MiddlewareError(String),
/// Occurs on the client if there is an error deserializing the server's response.
#[error("error deserializing server function results: {0}")]
Deserialization(String),
@@ -351,34 +403,6 @@ pub enum ServerFnErrorErr<E = NoCustomError> {
Response(String),
}
impl<CustErr> From<ServerFnError<CustErr>> for ServerFnErrorErr<CustErr> {
fn from(value: ServerFnError<CustErr>) -> Self {
match value {
ServerFnError::Registration(value) => {
ServerFnErrorErr::Registration(value)
}
ServerFnError::Request(value) => ServerFnErrorErr::Request(value),
ServerFnError::ServerError(value) => {
ServerFnErrorErr::ServerError(value)
}
ServerFnError::Deserialization(value) => {
ServerFnErrorErr::Deserialization(value)
}
ServerFnError::Serialization(value) => {
ServerFnErrorErr::Serialization(value)
}
ServerFnError::Args(value) => ServerFnErrorErr::Args(value),
ServerFnError::MissingArg(value) => {
ServerFnErrorErr::MissingArg(value)
}
ServerFnError::WrappedServerError(value) => {
ServerFnErrorErr::WrappedServerError(value)
}
ServerFnError::Response(value) => ServerFnErrorErr::Response(value),
}
}
}
/// Associates a particular server function error with the server function
/// found at a particular path.
///
@@ -386,15 +410,15 @@ impl<CustErr> From<ServerFnError<CustErr>> for ServerFnErrorErr<CustErr> {
/// without JavaScript/WASM supported, by encoding it in the URL as a query string.
/// This is useful for progressive enhancement.
#[derive(Debug)]
pub struct ServerFnUrlError<CustErr> {
pub struct ServerFnUrlError<E> {
path: String,
error: ServerFnError<CustErr>,
error: E,
}
impl<CustErr> ServerFnUrlError<CustErr> {
impl<E: FromServerFnError> ServerFnUrlError<E> {
/// Creates a new structure associating the server function at some path
/// with a particular error.
pub fn new(path: impl Display, error: ServerFnError<CustErr>) -> Self {
pub fn new(path: impl Display, error: E) -> Self {
Self {
path: path.to_string(),
error,
@@ -402,7 +426,7 @@ impl<CustErr> ServerFnUrlError<CustErr> {
}
/// The error itself.
pub fn error(&self) -> &ServerFnError<CustErr> {
pub fn error(&self) -> &E {
&self.error
}
@@ -412,17 +436,11 @@ impl<CustErr> ServerFnUrlError<CustErr> {
}
/// Adds an encoded form of this server function error to the given base URL.
pub fn to_url(&self, base: &str) -> Result<Url, url::ParseError>
where
CustErr: FromStr + Display,
{
pub fn to_url(&self, base: &str) -> Result<Url, url::ParseError> {
let mut url = Url::parse(base)?;
url.query_pairs_mut()
.append_pair("__path", &self.path)
.append_pair(
"__err",
&ServerFnErrorSerde::ser(&self.error).unwrap_or_default(),
);
.append_pair("__err", &URL_SAFE.encode(self.error.ser()));
Ok(url)
}
@@ -448,16 +466,102 @@ impl<CustErr> ServerFnUrlError<CustErr> {
*path = url.to_string();
}
}
/// Decodes an error from a URL.
pub fn decode_err(err: &str) -> E {
let decoded = match URL_SAFE.decode(err) {
Ok(decoded) => decoded,
Err(err) => {
return ServerFnErrorErr::Deserialization(err.to_string())
.into_app_error();
}
};
let s = match String::from_utf8(decoded) {
Ok(s) => s,
Err(err) => {
return ServerFnErrorErr::Deserialization(err.to_string())
.into_app_error();
}
};
E::de(&s)
}
}
impl<CustErr> From<ServerFnUrlError<CustErr>> for ServerFnError<CustErr> {
fn from(error: ServerFnUrlError<CustErr>) -> Self {
impl<E> From<ServerFnUrlError<E>> for ServerFnError<E> {
fn from(error: ServerFnUrlError<E>) -> Self {
error.error.into()
}
}
impl<E> From<ServerFnUrlError<ServerFnError<E>>> for ServerFnError<E> {
fn from(error: ServerFnUrlError<ServerFnError<E>>) -> Self {
error.error
}
}
impl<CustErr> From<ServerFnUrlError<CustErr>> for ServerFnErrorErr<CustErr> {
fn from(error: ServerFnUrlError<CustErr>) -> Self {
error.error.into()
#[derive(Debug)]
#[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())
}
}
impl<E: FromServerFnError> std::error::Error for ServerFnErrorWrapper<E> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
/// A trait for types that can be returned from a server function.
pub trait FromServerFnError:
std::fmt::Debug + Serialize + DeserializeOwned + 'static
{
/// Converts a [`ServerFnErrorErr`] into the application-specific custom error type.
fn from_server_fn_error(value: ServerFnErrorErr) -> Self;
/// Converts the custom error type to a [`String`]. Defaults to serializing to JSON.
fn ser(&self) -> String {
serde_json::to_string(self).unwrap_or_else(|e| {
serde_json::to_string(&Self::from_server_fn_error(
ServerFnErrorErr::Serialization(e.to_string()),
))
.expect(
"error serializing should success at least with the \
Serialization error",
)
})
}
/// Deserializes the custom error type from a [`&str`]. Defaults to deserializing from JSON.
fn de(data: &str) -> Self {
serde_json::from_str(data).unwrap_or_else(|e| {
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()
})
}
}
/// A helper trait for converting a [`ServerFnErrorErr`] into an application-specific custom error type that implements [`FromServerFnError`].
pub trait IntoAppError<E> {
/// Converts a [`ServerFnErrorErr`] into the application-specific custom error type.
fn into_app_error(self) -> E;
}
impl<E> IntoAppError<E> for ServerFnErrorErr
where
E: FromServerFnError,
{
fn into_app_error(self) -> E {
E::from_server_fn_error(self)
}
}
#[test]
fn assert_from_server_fn_error_impl() {
fn assert_impl<T: FromServerFnError>() {}
assert_impl::<ServerFnError>();
}

View File

@@ -132,15 +132,15 @@ use codec::{Encoding, FromReq, FromRes, IntoReq, IntoRes};
pub use const_format;
use dashmap::DashMap;
pub use error::ServerFnError;
use error::ServerFnErrorSerde;
#[cfg(feature = "form-redirects")]
use error::ServerFnUrlError;
use error::{FromServerFnError, ServerFnErrorErr};
use http::Method;
use middleware::{Layer, Service};
use middleware::{BoxedService, Layer, Service};
use once_cell::sync::Lazy;
use redirect::RedirectHook;
use request::Req;
use response::{ClientRes, Res};
use response::{ClientRes, Res, TryRes};
#[cfg(feature = "rkyv")]
pub use rkyv;
#[doc(hidden)]
@@ -148,7 +148,7 @@ pub use serde;
#[doc(hidden)]
#[cfg(feature = "serde-lite")]
pub use serde_lite;
use std::{fmt::Display, future::Future, pin::Pin, str::FromStr, sync::Arc};
use std::{future::Future, pin::Pin, sync::Arc};
#[doc(hidden)]
pub use xxhash_rust;
@@ -203,7 +203,7 @@ where
type ServerRequest: Req<Self::Error> + Send;
/// The type of the HTTP response returned by the server function on the server side.
type ServerResponse: Res<Self::Error> + Send;
type ServerResponse: Res + TryRes<Self::Error> + Send;
/// The return type of the server function.
///
@@ -222,9 +222,8 @@ where
/// The [`Encoding`] used in the response for the result of the server function.
type OutputEncoding: Encoding;
/// The type of the custom error on [`ServerFnError`], if any. (If there is no
/// custom error type, this can be `NoCustomError` by default.)
type Error: FromStr + Display;
/// The type of the error on the server function. Typically [`ServerFnError`], but allowed to be any type that implements [`FromServerFnError`].
type Error: FromServerFnError;
/// Returns [`Self::PATH`].
fn url() -> &'static str {
@@ -240,7 +239,7 @@ where
/// The body of the server function. This will only run on the server.
fn run_body(
self,
) -> impl Future<Output = Result<Self::Output, ServerFnError<Self::Error>>> + Send;
) -> impl Future<Output = Result<Self::Output, Self::Error>> + Send;
#[doc(hidden)]
fn run_on_server(
@@ -265,7 +264,10 @@ where
.map(|res| (res, None))
.unwrap_or_else(|e| {
(
Self::ServerResponse::error_response(Self::PATH, &e),
Self::ServerResponse::error_response(
Self::PATH,
e.ser(),
),
Some(e),
)
});
@@ -298,8 +300,7 @@ where
#[doc(hidden)]
fn run_on_client(
self,
) -> impl Future<Output = Result<Self::Output, ServerFnError<Self::Error>>> + Send
{
) -> impl Future<Output = Result<Self::Output, Self::Error>> + Send {
async move {
// create and send request on client
let req =
@@ -313,8 +314,7 @@ where
fn run_on_client_with_req(
req: <Self::Client as Client<Self::Error>>::Request,
redirect_hook: Option<&RedirectHook>,
) -> impl Future<Output = Result<Self::Output, ServerFnError<Self::Error>>> + Send
{
) -> impl Future<Output = Result<Self::Output, Self::Error>> + Send {
async move {
let res = Self::Client::send(req).await?;
@@ -325,7 +325,7 @@ where
// if it returns an error status, deserialize the error using FromStr
let res = if (400..=599).contains(&status) {
let text = res.try_into_string().await?;
Err(ServerFnError::<Self::Error>::de(&text))
Err(Self::Error::de(&text))
} else {
// otherwise, deserialize the body as is
Ok(Self::Output::from_res(res).await)
@@ -345,9 +345,8 @@ where
#[doc(hidden)]
fn execute_on_server(
req: Self::ServerRequest,
) -> impl Future<
Output = Result<Self::ServerResponse, ServerFnError<Self::Error>>,
> + Send {
) -> impl Future<Output = Result<Self::ServerResponse, Self::Error>> + Send
{
async {
let this = Self::from_req(req).await?;
let output = this.run_body().await?;
@@ -387,21 +386,20 @@ 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,
}
impl<Req, Res> ServerFnTraitObj<Req, Res> {
/// Converts the relevant parts of a server function into a trait object.
pub const fn new(
path: &'static str,
method: Method,
pub const fn new<S: ServerFn<ServerRequest = Req, ServerResponse = Res>>(
handler: fn(Req) -> Pin<Box<dyn Future<Output = Res> + Send>>,
middleware: fn() -> MiddlewareSet<Req, Res>,
) -> Self {
Self {
path,
method,
path: S::PATH,
method: S::InputEncoding::METHOD,
handler,
middleware,
middleware: S::middlewares,
ser: |e| S::Error::from_server_fn_error(e).ser(),
}
}
@@ -424,6 +422,16 @@ impl<Req, Res> ServerFnTraitObj<Req, Res> {
pub fn middleware(&self) -> MiddlewareSet<Req, Res> {
(self.middleware)()
}
/// Converts the server function into a boxed service.
pub fn boxed(self) -> BoxedService<Req, Res>
where
Self: Service<Req, Res>,
Req: 'static,
Res: 'static,
{
BoxedService::new(self.ser, self)
}
}
impl<Req, Res> Service<Req, Res> for ServerFnTraitObj<Req, Res>
@@ -431,7 +439,11 @@ where
Req: Send + 'static,
Res: 'static,
{
fn run(&mut self, req: Req) -> Pin<Box<dyn Future<Output = Res> + Send>> {
fn run(
&mut self,
req: Req,
_ser: fn(ServerFnErrorErr) -> String,
) -> Pin<Box<dyn Future<Output = Res> + Send>> {
let handler = self.handler;
Box::pin(async move { handler(req).await })
}
@@ -444,6 +456,7 @@ impl<Req, Res> Clone for ServerFnTraitObj<Req, Res> {
method: self.method.clone(),
handler: self.handler,
middleware: self.middleware,
ser: self.ser,
}
}
}
@@ -467,8 +480,8 @@ impl<Req: 'static, Res: 'static> inventory::Collect
#[cfg(feature = "axum-no-default")]
pub mod axum {
use crate::{
middleware::{BoxedService, Service},
Encoding, LazyServerFnMap, ServerFn, ServerFnTraitObj,
middleware::BoxedService, Encoding, LazyServerFnMap, ServerFn,
ServerFnTraitObj,
};
use axum::body::Body;
use http::{Method, Request, Response, StatusCode};
@@ -490,12 +503,7 @@ pub mod axum {
{
REGISTERED_SERVER_FUNCTIONS.insert(
(T::PATH.into(), T::InputEncoding::METHOD),
ServerFnTraitObj::new(
T::PATH,
T::InputEncoding::METHOD,
|req| Box::pin(T::run_on_server(req)),
T::middlewares,
),
ServerFnTraitObj::new::<T>(|req| Box::pin(T::run_on_server(req))),
);
}
@@ -539,7 +547,7 @@ pub mod axum {
let key = (path.into(), method);
REGISTERED_SERVER_FUNCTIONS.get(&key).map(|server_fn| {
let middleware = (server_fn.middleware)();
let mut service = BoxedService::new(server_fn.clone());
let mut service = server_fn.clone().boxed();
for middleware in middleware {
service = middleware.layer(service);
}
@@ -578,12 +586,7 @@ pub mod actix {
{
REGISTERED_SERVER_FUNCTIONS.insert(
(T::PATH.into(), T::InputEncoding::METHOD),
ServerFnTraitObj::new(
T::PATH,
T::InputEncoding::METHOD,
|req| Box::pin(T::run_on_server(req)),
T::middlewares,
),
ServerFnTraitObj::new::<T>(|req| Box::pin(T::run_on_server(req))),
);
}
@@ -603,7 +606,6 @@ pub mod actix {
let method = req.method();
if let Some(mut service) = get_server_fn_service(path, method) {
service
.0
.run(ActixRequest::from((req, payload)))
.await
.0
@@ -644,7 +646,7 @@ pub mod actix {
REGISTERED_SERVER_FUNCTIONS.get(&(path.into(), method)).map(
|server_fn| {
let middleware = (server_fn.middleware)();
let mut service = BoxedService::new(server_fn.clone());
let mut service = server_fn.clone().boxed();
for middleware in middleware {
service = middleware.layer(service);
}

View File

@@ -1,3 +1,4 @@
use crate::error::ServerFnErrorErr;
use std::{future::Future, pin::Pin};
/// An abstraction over a middleware layer, which can be used to add additional
@@ -8,12 +9,31 @@ pub trait Layer<Req, Res>: Send + Sync + 'static {
}
/// A type-erased service, which takes an HTTP request and returns a response.
pub struct BoxedService<Req, Res>(pub Box<dyn Service<Req, Res> + Send>);
pub struct BoxedService<Req, Res> {
/// A function that converts a [`ServerFnErrorErr`] into a string.
pub ser: fn(ServerFnErrorErr) -> String,
/// The inner service.
pub service: Box<dyn Service<Req, Res> + Send>,
}
impl<Req, Res> BoxedService<Req, Res> {
/// Constructs a type-erased service from this service.
pub fn new(service: impl Service<Req, Res> + Send + 'static) -> Self {
Self(Box::new(service))
pub fn new(
ser: fn(ServerFnErrorErr) -> String,
service: impl Service<Req, Res> + Send + 'static,
) -> Self {
Self {
ser,
service: Box::new(service),
}
}
/// Converts a request into a response by running the inner service.
pub fn run(
&mut self,
req: Req,
) -> Pin<Box<dyn Future<Output = Res> + Send>> {
self.service.run(req, self.ser)
}
}
@@ -23,37 +43,36 @@ pub trait Service<Request, Response> {
fn run(
&mut self,
req: Request,
ser: fn(ServerFnErrorErr) -> String,
) -> Pin<Box<dyn Future<Output = Response> + Send>>;
}
#[cfg(feature = "axum-no-default")]
mod axum {
use super::{BoxedService, Service};
use crate::{response::Res, ServerFnError};
use crate::{error::ServerFnErrorErr, response::Res, ServerFnError};
use axum::body::Body;
use http::{Request, Response};
use std::{
fmt::{Debug, Display},
future::Future,
pin::Pin,
};
use std::{future::Future, pin::Pin};
impl<S> super::Service<Request<Body>, Response<Body>> for S
where
S: tower::Service<Request<Body>, Response = Response<Body>>,
S::Future: Send + 'static,
S::Error: Into<ServerFnError> + Send + Debug + Display + Sync + 'static,
S::Error: std::fmt::Display + Send + 'static,
{
fn run(
&mut self,
req: Request<Body>,
ser: fn(ServerFnErrorErr) -> String,
) -> Pin<Box<dyn Future<Output = Response<Body>> + Send>> {
let path = req.uri().path().to_string();
let inner = self.call(req);
Box::pin(async move {
inner.await.unwrap_or_else(|e| {
let err = ServerFnError::new(e);
Response::<Body>::error_response(&path, &err)
let err =
ser(ServerFnErrorErr::MiddlewareError(e.to_string()));
Response::<Body>::error_response(&path, err)
})
})
}
@@ -80,7 +99,7 @@ mod axum {
}
fn call(&mut self, req: Request<Body>) -> Self::Future {
let inner = self.0.run(req);
let inner = self.service.run(req, self.ser);
Box::pin(async move { Ok(inner.await) })
}
}
@@ -97,7 +116,7 @@ mod axum {
&self,
inner: BoxedService<Request<Body>, Response<Body>>,
) -> BoxedService<Request<Body>, Response<Body>> {
BoxedService(Box::new(self.layer(inner)))
BoxedService::new(inner.ser, self.layer(inner))
}
}
}
@@ -105,33 +124,31 @@ mod axum {
#[cfg(feature = "actix")]
mod actix {
use crate::{
error::ServerFnErrorErr,
request::actix::ActixRequest,
response::{actix::ActixResponse, Res},
ServerFnError,
};
use actix_web::{HttpRequest, HttpResponse};
use std::{
fmt::{Debug, Display},
future::Future,
pin::Pin,
};
use std::{future::Future, pin::Pin};
impl<S> super::Service<HttpRequest, HttpResponse> for S
where
S: actix_web::dev::Service<HttpRequest, Response = HttpResponse>,
S::Future: Send + 'static,
S::Error: Into<ServerFnError> + Debug + Display + 'static,
S::Error: std::fmt::Display + Send + 'static,
{
fn run(
&mut self,
req: HttpRequest,
ser: fn(ServerFnErrorErr) -> String,
) -> Pin<Box<dyn Future<Output = HttpResponse> + Send>> {
let path = req.uri().path().to_string();
let inner = self.call(req);
Box::pin(async move {
inner.await.unwrap_or_else(|e| {
let err = ServerFnError::new(e);
ActixResponse::error_response(&path, &err).take()
let err =
ser(ServerFnErrorErr::MiddlewareError(e.to_string()));
ActixResponse::error_response(&path, err).take()
})
})
}
@@ -141,18 +158,20 @@ mod actix {
where
S: actix_web::dev::Service<HttpRequest, Response = HttpResponse>,
S::Future: Send + 'static,
S::Error: Into<ServerFnError> + Debug + Display + 'static,
S::Error: std::fmt::Display + Send + 'static,
{
fn run(
&mut self,
req: ActixRequest,
ser: fn(ServerFnErrorErr) -> String,
) -> Pin<Box<dyn Future<Output = ActixResponse> + Send>> {
let path = req.0 .0.uri().path().to_string();
let inner = self.call(req.0.take().0);
Box::pin(async move {
ActixResponse::from(inner.await.unwrap_or_else(|e| {
let err = ServerFnError::new(e);
ActixResponse::error_response(&path, &err).take()
let err =
ser(ServerFnErrorErr::MiddlewareError(e.to_string()));
ActixResponse::error_response(&path, err).take()
}))
})
}

View File

@@ -1,4 +1,7 @@
use crate::{error::ServerFnError, request::Req};
use crate::{
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
request::Req,
};
use actix_web::{web::Payload, HttpRequest};
use bytes::Bytes;
use futures::{Stream, StreamExt};
@@ -33,9 +36,9 @@ impl From<(HttpRequest, Payload)> for ActixRequest {
}
}
impl<CustErr> Req<CustErr> for ActixRequest
impl<E> Req<E> for ActixRequest
where
CustErr: 'static,
E: FromServerFnError,
{
fn as_query(&self) -> Option<&str> {
self.0 .0.uri().query()
@@ -53,44 +56,39 @@ where
self.header("Referer")
}
fn try_into_bytes(
self,
) -> impl Future<Output = Result<Bytes, ServerFnError<CustErr>>> + Send
{
fn try_into_bytes(self) -> impl Future<Output = Result<Bytes, E>> + Send {
// Actix is going to keep this on a single thread anyway so it's fine to wrap it
// with SendWrapper, which makes it `Send` but will panic if it moves to another thread
SendWrapper::new(async move {
let payload = self.0.take().1;
payload
.to_bytes()
.await
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
payload.to_bytes().await.map_err(|e| {
ServerFnErrorErr::Deserialization(e.to_string())
.into_app_error()
})
})
}
fn try_into_string(
self,
) -> impl Future<Output = Result<String, ServerFnError<CustErr>>> + Send
{
fn try_into_string(self) -> impl Future<Output = Result<String, E>> + Send {
// Actix is going to keep this on a single thread anyway so it's fine to wrap it
// with SendWrapper, which makes it `Send` but will panic if it moves to another thread
SendWrapper::new(async move {
let payload = self.0.take().1;
let bytes = payload
.to_bytes()
.await
.map_err(|e| ServerFnError::Deserialization(e.to_string()))?;
String::from_utf8(bytes.into())
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
let bytes = payload.to_bytes().await.map_err(|e| {
E::from_server_fn_error(ServerFnErrorErr::Deserialization(
e.to_string(),
))
})?;
String::from_utf8(bytes.into()).map_err(|e| {
E::from_server_fn_error(ServerFnErrorErr::Deserialization(
e.to_string(),
))
})
})
}
fn try_into_stream(
self,
) -> Result<
impl Stream<Item = Result<Bytes, ServerFnError>> + Send,
ServerFnError<CustErr>,
> {
) -> Result<impl Stream<Item = Result<Bytes, E>> + Send, E> {
let payload = self.0.take().1;
let stream = payload.map(|res| {
res.map_err(|e| ServerFnError::Deserialization(e.to_string()))

View File

@@ -1,4 +1,7 @@
use crate::{error::ServerFnError, request::Req};
use crate::{
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
request::Req,
};
use axum::body::{Body, Bytes};
use futures::{Stream, StreamExt};
use http::{
@@ -8,9 +11,9 @@ use http::{
use http_body_util::BodyExt;
use std::borrow::Cow;
impl<CustErr> Req<CustErr> for Request<Body>
impl<E> Req<E> for Request<Body>
where
CustErr: 'static,
E: FromServerFnError,
{
fn as_query(&self) -> Option<&str> {
self.uri().query()
@@ -34,29 +37,29 @@ where
.map(|h| String::from_utf8_lossy(h.as_bytes()))
}
async fn try_into_bytes(self) -> Result<Bytes, ServerFnError<CustErr>> {
async fn try_into_bytes(self) -> Result<Bytes, E> {
let (_parts, body) = self.into_parts();
body.collect()
.await
.map(|c| c.to_bytes())
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
body.collect().await.map(|c| c.to_bytes()).map_err(|e| {
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()
})
}
async fn try_into_string(self) -> Result<String, ServerFnError<CustErr>> {
async fn try_into_string(self) -> Result<String, E> {
let bytes = self.try_into_bytes().await?;
String::from_utf8(bytes.to_vec())
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
String::from_utf8(bytes.to_vec()).map_err(|e| {
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()
})
}
fn try_into_stream(
self,
) -> Result<
impl Stream<Item = Result<Bytes, ServerFnError>> + Send + 'static,
ServerFnError<CustErr>,
> {
) -> Result<impl Stream<Item = Result<Bytes, E>> + Send + 'static, E> {
Ok(self.into_body().into_data_stream().map(|chunk| {
chunk.map_err(|e| ServerFnError::Deserialization(e.to_string()))
chunk.map_err(|e| {
ServerFnErrorErr::Deserialization(e.to_string())
.into_app_error()
})
}))
}
}

View File

@@ -1,5 +1,8 @@
use super::ClientReq;
use crate::{client::get_server_url, error::ServerFnError};
use crate::{
client::get_server_url,
error::{FromServerFnError, ServerFnErrorErr},
};
use bytes::Bytes;
use futures::{Stream, StreamExt};
pub use gloo_net::http::Request;
@@ -83,7 +86,10 @@ fn abort_signal() -> (Option<AbortOnDrop>, Option<AbortSignal>) {
(ctrl.map(|ctrl| AbortOnDrop(Some(ctrl))), signal)
}
impl<CustErr> ClientReq<CustErr> for BrowserRequest {
impl<E> ClientReq<E> for BrowserRequest
where
E: FromServerFnError,
{
type FormData = BrowserFormData;
fn try_new_get(
@@ -91,7 +97,7 @@ impl<CustErr> ClientReq<CustErr> for BrowserRequest {
accepts: &str,
content_type: &str,
query: &str,
) -> Result<Self, ServerFnError<CustErr>> {
) -> Result<Self, E> {
let (abort_ctrl, abort_signal) = abort_signal();
let server_url = get_server_url();
let mut url = String::with_capacity(
@@ -107,7 +113,11 @@ impl<CustErr> ClientReq<CustErr> for BrowserRequest {
.header("Accept", accepts)
.abort_signal(abort_signal.as_ref())
.build()
.map_err(|e| ServerFnError::Request(e.to_string()))?,
.map_err(|e| {
E::from_server_fn_error(ServerFnErrorErr::Request(
e.to_string(),
))
})?,
abort_ctrl,
})))
}
@@ -117,7 +127,7 @@ impl<CustErr> ClientReq<CustErr> for BrowserRequest {
accepts: &str,
content_type: &str,
body: String,
) -> Result<Self, ServerFnError<CustErr>> {
) -> Result<Self, E> {
let (abort_ctrl, abort_signal) = abort_signal();
let server_url = get_server_url();
let mut url = String::with_capacity(server_url.len() + path.len());
@@ -129,7 +139,11 @@ impl<CustErr> ClientReq<CustErr> for BrowserRequest {
.header("Accept", accepts)
.abort_signal(abort_signal.as_ref())
.body(body)
.map_err(|e| ServerFnError::Request(e.to_string()))?,
.map_err(|e| {
E::from_server_fn_error(ServerFnErrorErr::Request(
e.to_string(),
))
})?,
abort_ctrl,
})))
}
@@ -139,7 +153,7 @@ impl<CustErr> ClientReq<CustErr> for BrowserRequest {
accepts: &str,
content_type: &str,
body: Bytes,
) -> Result<Self, ServerFnError<CustErr>> {
) -> Result<Self, E> {
let (abort_ctrl, abort_signal) = abort_signal();
let server_url = get_server_url();
let mut url = String::with_capacity(server_url.len() + path.len());
@@ -153,7 +167,11 @@ impl<CustErr> ClientReq<CustErr> for BrowserRequest {
.header("Accept", accepts)
.abort_signal(abort_signal.as_ref())
.body(body)
.map_err(|e| ServerFnError::Request(e.to_string()))?,
.map_err(|e| {
E::from_server_fn_error(ServerFnErrorErr::Request(
e.to_string(),
))
})?,
abort_ctrl,
})))
}
@@ -162,7 +180,7 @@ impl<CustErr> ClientReq<CustErr> for BrowserRequest {
path: &str,
accepts: &str,
body: Self::FormData,
) -> Result<Self, ServerFnError<CustErr>> {
) -> Result<Self, E> {
let (abort_ctrl, abort_signal) = abort_signal();
let server_url = get_server_url();
let mut url = String::with_capacity(server_url.len() + path.len());
@@ -173,7 +191,11 @@ impl<CustErr> ClientReq<CustErr> for BrowserRequest {
.header("Accept", accepts)
.abort_signal(abort_signal.as_ref())
.body(body.0.take())
.map_err(|e| ServerFnError::Request(e.to_string()))?,
.map_err(|e| {
E::from_server_fn_error(ServerFnErrorErr::Request(
e.to_string(),
))
})?,
abort_ctrl,
})))
}
@@ -183,17 +205,17 @@ impl<CustErr> ClientReq<CustErr> for BrowserRequest {
accepts: &str,
content_type: &str,
body: Self::FormData,
) -> Result<Self, ServerFnError<CustErr>> {
) -> Result<Self, E> {
let (abort_ctrl, abort_signal) = abort_signal();
let form_data = body.0.take();
let url_params =
UrlSearchParams::new_with_str_sequence_sequence(&form_data)
.map_err(|e| {
ServerFnError::Serialization(e.as_string().unwrap_or_else(
|| {
E::from_server_fn_error(ServerFnErrorErr::Serialization(
e.as_string().unwrap_or_else(|| {
"Could not serialize FormData to URLSearchParams"
.to_string()
},
}),
))
})?;
Ok(Self(SendWrapper::new(RequestInner {
@@ -202,7 +224,11 @@ impl<CustErr> ClientReq<CustErr> for BrowserRequest {
.header("Accept", accepts)
.abort_signal(abort_signal.as_ref())
.body(url_params)
.map_err(|e| ServerFnError::Request(e.to_string()))?,
.map_err(|e| {
E::from_server_fn_error(ServerFnErrorErr::Request(
e.to_string(),
))
})?,
abort_ctrl,
})))
}
@@ -212,11 +238,16 @@ impl<CustErr> ClientReq<CustErr> for BrowserRequest {
accepts: &str,
content_type: &str,
body: impl Stream<Item = Bytes> + 'static,
) -> Result<Self, ServerFnError<CustErr>> {
) -> Result<Self, E> {
// TODO abort signal
let (request, abort_ctrl) =
streaming_request(path, accepts, content_type, body)
.map_err(|e| ServerFnError::Request(format!("{e:?}")))?;
streaming_request(path, accepts, content_type, body).map_err(
|e| {
E::from_server_fn_error(ServerFnErrorErr::Request(format!(
"{e:?}"
)))
},
)?;
Ok(Self(SendWrapper::new(RequestInner {
request,
abort_ctrl,

View File

@@ -12,7 +12,10 @@
//! * `wasm32-wasip*` integration crate `leptos_wasi` is using this
//! crate under the hood.
use crate::request::Req;
use crate::{
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
request::Req,
};
use bytes::Bytes;
use futures::{
stream::{self, Stream},
@@ -21,30 +24,23 @@ use futures::{
use http::Request;
use std::borrow::Cow;
impl<CustErr> Req<CustErr> for Request<Bytes>
impl<E> Req<E> for Request<Bytes>
where
CustErr: 'static,
E: FromServerFnError,
{
async fn try_into_bytes(
self,
) -> Result<Bytes, crate::ServerFnError<CustErr>> {
async fn try_into_bytes(self) -> Result<Bytes, E> {
Ok(self.into_body())
}
async fn try_into_string(
self,
) -> Result<String, crate::ServerFnError<CustErr>> {
async fn try_into_string(self) -> Result<String, E> {
String::from_utf8(self.into_body().into()).map_err(|err| {
crate::ServerFnError::Deserialization(err.to_string())
ServerFnErrorErr::Deserialization(err.to_string()).into_app_error()
})
}
fn try_into_stream(
self,
) -> Result<
impl Stream<Item = Result<Bytes, crate::ServerFnError>> + Send + 'static,
crate::ServerFnError<CustErr>,
> {
) -> Result<impl Stream<Item = Result<Bytes, E>> + Send + 'static, E> {
Ok(stream::iter(self.into_body())
.ready_chunks(16)
.map(|chunk| Ok(Bytes::from(chunk))))

View File

@@ -1,4 +1,3 @@
use crate::error::ServerFnError;
use bytes::Bytes;
use futures::Stream;
use std::{borrow::Cow, future::Future};
@@ -19,7 +18,7 @@ pub mod generic;
pub mod reqwest;
/// Represents a request as made by the client.
pub trait ClientReq<CustErr>
pub trait ClientReq<E>
where
Self: Sized,
{
@@ -32,7 +31,7 @@ where
content_type: &str,
accepts: &str,
query: &str,
) -> Result<Self, ServerFnError<CustErr>>;
) -> Result<Self, E>;
/// Attempts to construct a new `POST` request with a text body.
fn try_new_post(
@@ -40,7 +39,7 @@ where
content_type: &str,
accepts: &str,
body: String,
) -> Result<Self, ServerFnError<CustErr>>;
) -> Result<Self, E>;
/// Attempts to construct a new `POST` request with a binary body.
fn try_new_post_bytes(
@@ -48,7 +47,7 @@ where
content_type: &str,
accepts: &str,
body: Bytes,
) -> Result<Self, ServerFnError<CustErr>>;
) -> Result<Self, E>;
/// Attempts to construct a new `POST` request with form data as the body.
fn try_new_post_form_data(
@@ -56,14 +55,14 @@ where
accepts: &str,
content_type: &str,
body: Self::FormData,
) -> Result<Self, ServerFnError<CustErr>>;
) -> Result<Self, E>;
/// Attempts to construct a new `POST` request with a multipart body.
fn try_new_multipart(
path: &str,
accepts: &str,
body: Self::FormData,
) -> Result<Self, ServerFnError<CustErr>>;
) -> Result<Self, E>;
/// Attempts to construct a new `POST` request with a streaming body.
fn try_new_streaming(
@@ -71,11 +70,11 @@ where
accepts: &str,
content_type: &str,
body: impl Stream<Item = Bytes> + Send + 'static,
) -> Result<Self, ServerFnError<CustErr>>;
) -> Result<Self, E>;
}
/// Represents the request as received by the server.
pub trait Req<CustErr>
pub trait Req<E>
where
Self: Sized,
{
@@ -92,32 +91,22 @@ where
fn referer(&self) -> Option<Cow<'_, str>>;
/// Attempts to extract the body of the request into [`Bytes`].
fn try_into_bytes(
self,
) -> impl Future<Output = Result<Bytes, ServerFnError<CustErr>>> + Send;
fn try_into_bytes(self) -> impl Future<Output = Result<Bytes, E>> + Send;
/// Attempts to convert the body of the request into a string.
fn try_into_string(
self,
) -> impl Future<Output = Result<String, ServerFnError<CustErr>>> + Send;
fn try_into_string(self) -> impl Future<Output = Result<String, E>> + Send;
/// Attempts to convert the body of the request into a stream of bytes.
fn try_into_stream(
self,
) -> Result<
impl Stream<Item = Result<Bytes, ServerFnError>> + Send + 'static,
ServerFnError<CustErr>,
>;
) -> Result<impl Stream<Item = Result<Bytes, E>> + Send + 'static, E>;
}
/// A mocked request type that can be used in place of the actual server request,
/// when compiling for the browser.
pub struct BrowserMockReq;
impl<CustErr> Req<CustErr> for BrowserMockReq
where
CustErr: 'static,
{
impl<E: 'static> Req<E> for BrowserMockReq {
fn as_query(&self) -> Option<&str> {
unreachable!()
}
@@ -133,20 +122,17 @@ where
fn referer(&self) -> Option<Cow<'_, str>> {
unreachable!()
}
async fn try_into_bytes(self) -> Result<Bytes, ServerFnError<CustErr>> {
async fn try_into_bytes(self) -> Result<Bytes, E> {
unreachable!()
}
async fn try_into_string(self) -> Result<String, ServerFnError<CustErr>> {
async fn try_into_string(self) -> Result<String, E> {
unreachable!()
}
fn try_into_stream(
self,
) -> Result<
impl Stream<Item = Result<Bytes, ServerFnError>> + Send,
ServerFnError<CustErr>,
> {
) -> Result<impl Stream<Item = Result<Bytes, E>> + Send, E> {
Ok(futures::stream::once(async { unreachable!() }))
}
}

View File

@@ -1,5 +1,8 @@
use super::ClientReq;
use crate::{client::get_server_url, error::ServerFnError};
use crate::{
client::get_server_url,
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
};
use bytes::Bytes;
use futures::Stream;
use once_cell::sync::Lazy;
@@ -8,7 +11,10 @@ pub use reqwest::{multipart::Form, Client, Method, Request, Url};
pub(crate) static CLIENT: Lazy<Client> = Lazy::new(Client::new);
impl<CustErr> ClientReq<CustErr> for Request {
impl<E> ClientReq<E> for Request
where
E: FromServerFnError,
{
type FormData = Form;
fn try_new_get(
@@ -16,17 +22,22 @@ impl<CustErr> ClientReq<CustErr> for Request {
accepts: &str,
content_type: &str,
query: &str,
) -> Result<Self, ServerFnError<CustErr>> {
) -> Result<Self, E> {
let url = format!("{}{}", get_server_url(), path);
let mut url = Url::try_from(url.as_str())
.map_err(|e| ServerFnError::Request(e.to_string()))?;
let mut url = Url::try_from(url.as_str()).map_err(|e| {
E::from_server_fn_error(ServerFnErrorErr::Request(e.to_string()))
})?;
url.set_query(Some(query));
let req = CLIENT
.get(url)
.header(CONTENT_TYPE, content_type)
.header(ACCEPT, accepts)
.build()
.map_err(|e| ServerFnError::Request(e.to_string()))?;
.map_err(|e| {
E::from_server_fn_error(ServerFnErrorErr::Request(
e.to_string(),
))
})?;
Ok(req)
}
@@ -35,7 +46,7 @@ impl<CustErr> ClientReq<CustErr> for Request {
accepts: &str,
content_type: &str,
body: String,
) -> Result<Self, ServerFnError<CustErr>> {
) -> Result<Self, E> {
let url = format!("{}{}", get_server_url(), path);
CLIENT
.post(url)
@@ -43,7 +54,9 @@ impl<CustErr> ClientReq<CustErr> for Request {
.header(ACCEPT, accepts)
.body(body)
.build()
.map_err(|e| ServerFnError::Request(e.to_string()))
.map_err(|e| {
ServerFnErrorErr::Request(e.to_string()).into_app_error()
})
}
fn try_new_post_bytes(
@@ -51,7 +64,7 @@ impl<CustErr> ClientReq<CustErr> for Request {
accepts: &str,
content_type: &str,
body: Bytes,
) -> Result<Self, ServerFnError<CustErr>> {
) -> Result<Self, E> {
let url = format!("{}{}", get_server_url(), path);
CLIENT
.post(url)
@@ -59,20 +72,24 @@ impl<CustErr> ClientReq<CustErr> for Request {
.header(ACCEPT, accepts)
.body(body)
.build()
.map_err(|e| ServerFnError::Request(e.to_string()))
.map_err(|e| {
ServerFnErrorErr::Request(e.to_string()).into_app_error()
})
}
fn try_new_multipart(
path: &str,
accepts: &str,
body: Self::FormData,
) -> Result<Self, ServerFnError<CustErr>> {
) -> Result<Self, E> {
CLIENT
.post(path)
.header(ACCEPT, accepts)
.multipart(body)
.build()
.map_err(|e| ServerFnError::Request(e.to_string()))
.map_err(|e| {
ServerFnErrorErr::Request(e.to_string()).into_app_error()
})
}
fn try_new_post_form_data(
@@ -80,14 +97,16 @@ impl<CustErr> ClientReq<CustErr> for Request {
accepts: &str,
content_type: &str,
body: Self::FormData,
) -> Result<Self, ServerFnError<CustErr>> {
) -> Result<Self, E> {
CLIENT
.post(path)
.header(CONTENT_TYPE, content_type)
.header(ACCEPT, accepts)
.multipart(body)
.build()
.map_err(|e| ServerFnError::Request(e.to_string()))
.map_err(|e| {
ServerFnErrorErr::Request(e.to_string()).into_app_error()
})
}
fn try_new_streaming(
@@ -95,7 +114,7 @@ impl<CustErr> ClientReq<CustErr> for Request {
_accepts: &str,
_content_type: &str,
_body: impl Stream<Item = Bytes> + 'static,
) -> Result<Self, ServerFnError<CustErr>> {
) -> Result<Self, E> {
todo!("Streaming requests are not yet implemented for reqwest.")
// We run into a fundamental issue here.
// To be a reqwest body, the type must be Sync
@@ -112,7 +131,7 @@ impl<CustErr> ClientReq<CustErr> for Request {
.header(ACCEPT, accepts)
.body(body)
.build()
.map_err(|e| ServerFnError::Request(e.to_string()))
.map_err(|e| ServerFnErrorErr::Request(e.to_string()).into())
}*/
}
}

View File

@@ -8,7 +8,7 @@ use http::{
use http_body_util::BodyExt;
use std::borrow::Cow;
impl<CustErr> Req<CustErr> for IncomingRequest
impl<E> Req<E> for IncomingRequest
where
CustErr: 'static,
{
@@ -34,29 +34,31 @@ where
.map(|h| String::from_utf8_lossy(h.as_bytes()))
}
async fn try_into_bytes(self) -> Result<Bytes, ServerFnError<CustErr>> {
async fn try_into_bytes(self) -> Result<Bytes, E> {
let (_parts, body) = self.into_parts();
body.collect()
.await
.map(|c| c.to_bytes())
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
body.collect().await.map(|c| c.to_bytes()).map_err(|e| {
ServerFnErrorErr::Deserialization(e.to_string()).into()
})
}
async fn try_into_string(self) -> Result<String, ServerFnError<CustErr>> {
async fn try_into_string(self) -> Result<String, E> {
let bytes = self.try_into_bytes().await?;
String::from_utf8(bytes.to_vec())
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
String::from_utf8(bytes.to_vec()).map_err(|e| {
ServerFnErrorErr::Deserialization(e.to_string()).into()
})
}
fn try_into_stream(
self,
) -> Result<
impl Stream<Item = Result<Bytes, ServerFnError>> + Send + 'static,
ServerFnError<CustErr>,
E,
> {
Ok(self.into_body().into_data_stream().map(|chunk| {
chunk.map_err(|e| ServerFnError::Deserialization(e.to_string()))
chunk.map_err(|e| {
ServerFnErrorErr::Deserialization(e.to_string()).into()
})
}))
}
}

View File

@@ -1,6 +1,6 @@
use super::Res;
use super::{Res, TryRes};
use crate::error::{
ServerFnError, ServerFnErrorErr, ServerFnErrorSerde, SERVER_FN_ERROR_HEADER,
FromServerFnError, ServerFnErrorWrapper, SERVER_FN_ERROR_HEADER,
};
use actix_web::{
http::{
@@ -13,10 +13,6 @@ use actix_web::{
use bytes::Bytes;
use futures::{Stream, StreamExt};
use send_wrapper::SendWrapper;
use std::{
fmt::{Debug, Display},
str::FromStr,
};
/// A wrapped Actix response.
///
@@ -38,14 +34,11 @@ impl From<HttpResponse> for ActixResponse {
}
}
impl<CustErr> Res<CustErr> for ActixResponse
impl<E> TryRes<E> for ActixResponse
where
CustErr: FromStr + Display + Debug + 'static,
E: FromServerFnError,
{
fn try_from_string(
content_type: &str,
data: String,
) -> Result<Self, ServerFnError<CustErr>> {
fn try_from_string(content_type: &str, data: String) -> Result<Self, E> {
let mut builder = HttpResponse::build(StatusCode::OK);
Ok(ActixResponse(SendWrapper::new(
builder
@@ -54,10 +47,7 @@ where
)))
}
fn try_from_bytes(
content_type: &str,
data: Bytes,
) -> Result<Self, ServerFnError<CustErr>> {
fn try_from_bytes(content_type: &str, data: Bytes) -> Result<Self, E> {
let mut builder = HttpResponse::build(StatusCode::OK);
Ok(ActixResponse(SendWrapper::new(
builder
@@ -68,23 +58,23 @@ where
fn try_from_stream(
content_type: &str,
data: impl Stream<Item = Result<Bytes, ServerFnError<CustErr>>> + 'static,
) -> Result<Self, ServerFnError<CustErr>> {
data: impl Stream<Item = Result<Bytes, E>> + '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(ServerFnErrorErr::from)),
),
.streaming(data.map(|data| data.map_err(ServerFnErrorWrapper))),
)))
}
}
fn error_response(path: &str, err: &ServerFnError<CustErr>) -> Self {
impl Res for ActixResponse {
fn error_response(path: &str, err: String) -> Self {
ActixResponse(SendWrapper::new(
HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR)
.append_header((SERVER_FN_ERROR_HEADER, path))
.body(err.ser().unwrap_or_else(|_| err.to_string())),
.body(err),
))
}

View File

@@ -1,5 +1,8 @@
use super::ClientRes;
use crate::{error::ServerFnError, redirect::REDIRECT_HEADER};
use crate::{
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
redirect::REDIRECT_HEADER,
};
use bytes::Bytes;
use futures::{Stream, StreamExt};
pub use gloo_net::http::Response;
@@ -12,48 +15,39 @@ use wasm_streams::ReadableStream;
/// The response to a `fetch` request made in the browser.
pub struct BrowserResponse(pub(crate) SendWrapper<Response>);
impl<CustErr> ClientRes<CustErr> for BrowserResponse {
fn try_into_string(
self,
) -> impl Future<Output = Result<String, ServerFnError<CustErr>>> + Send
{
impl<E: FromServerFnError> ClientRes<E> for BrowserResponse {
fn try_into_string(self) -> impl Future<Output = Result<String, E>> + Send {
// the browser won't send this async work between threads (because it's single-threaded)
// so we can safely wrap this
SendWrapper::new(async move {
self.0
.text()
.await
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
self.0.text().await.map_err(|e| {
ServerFnErrorErr::Deserialization(e.to_string())
.into_app_error()
})
})
}
fn try_into_bytes(
self,
) -> impl Future<Output = Result<Bytes, ServerFnError<CustErr>>> + Send
{
fn try_into_bytes(self) -> impl Future<Output = Result<Bytes, E>> + Send {
// the browser won't send this async work between threads (because it's single-threaded)
// so we can safely wrap this
SendWrapper::new(async move {
self.0
.binary()
.await
.map(Bytes::from)
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
self.0.binary().await.map(Bytes::from).map_err(|e| {
ServerFnErrorErr::Deserialization(e.to_string())
.into_app_error()
})
})
}
fn try_into_stream(
self,
) -> Result<
impl Stream<Item = Result<Bytes, ServerFnError>> + Send + 'static,
ServerFnError<CustErr>,
> {
) -> Result<impl Stream<Item = Result<Bytes, E>> + Send + 'static, E> {
let stream = ReadableStream::from_raw(self.0.body().unwrap())
.into_stream()
.map(|data| match data {
Err(e) => {
web_sys::console::error_1(&e);
Err(ServerFnError::Request(format!("{e:?}")))
Err(ServerFnErrorErr::Request(format!("{e:?}"))
.into_app_error())
}
Ok(data) => {
let data = data.unchecked_into::<Uint8Array>();

View File

@@ -12,18 +12,15 @@
//! * `wasm32-wasip*` integration crate `leptos_wasi` is using this
//! crate under the hood.
use super::Res;
use super::{Res, TryRes};
use crate::error::{
ServerFnError, ServerFnErrorErr, ServerFnErrorSerde, SERVER_FN_ERROR_HEADER,
FromServerFnError, IntoAppError, ServerFnErrorErr, ServerFnErrorWrapper,
SERVER_FN_ERROR_HEADER,
};
use bytes::Bytes;
use futures::{Stream, TryStreamExt};
use http::{header, HeaderValue, Response, StatusCode};
use std::{
fmt::{Debug, Display},
pin::Pin,
str::FromStr,
};
use std::pin::Pin;
use throw_error::Error;
/// The Body of a Response whose *execution model* can be
@@ -44,55 +41,55 @@ impl From<String> for Body {
}
}
impl<CustErr> Res<CustErr> for Response<Body>
impl<E> TryRes<E> for Response<Body>
where
CustErr: Send + Sync + Debug + FromStr + Display + 'static,
E: Send + Sync + FromServerFnError,
{
fn try_from_string(
content_type: &str,
data: String,
) -> Result<Self, ServerFnError<CustErr>> {
fn try_from_string(content_type: &str, data: String) -> Result<Self, E> {
let builder = http::Response::builder();
builder
.status(200)
.header(http::header::CONTENT_TYPE, content_type)
.body(data.into())
.map_err(|e| ServerFnError::Response(e.to_string()))
.map_err(|e| {
ServerFnErrorErr::Response(e.to_string()).into_app_error()
})
}
fn try_from_bytes(
content_type: &str,
data: Bytes,
) -> Result<Self, ServerFnError<CustErr>> {
fn try_from_bytes(content_type: &str, data: Bytes) -> Result<Self, E> {
let builder = http::Response::builder();
builder
.status(200)
.header(http::header::CONTENT_TYPE, content_type)
.body(Body::Sync(data))
.map_err(|e| ServerFnError::Response(e.to_string()))
.map_err(|e| {
ServerFnErrorErr::Response(e.to_string()).into_app_error()
})
}
fn try_from_stream(
content_type: &str,
data: impl Stream<Item = Result<Bytes, ServerFnError<CustErr>>>
+ Send
+ 'static,
) -> Result<Self, ServerFnError<CustErr>> {
data: impl Stream<Item = Result<Bytes, E>> + 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(ServerFnErrorErr::from).map_err(Error::from),
data.map_err(ServerFnErrorWrapper).map_err(Error::from),
)))
.map_err(|e| ServerFnError::Response(e.to_string()))
.map_err(|e| {
ServerFnErrorErr::Response(e.to_string()).into_app_error()
})
}
}
fn error_response(path: &str, err: &ServerFnError<CustErr>) -> Self {
impl Res for Response<Body> {
fn error_response(path: &str, err: String) -> Self {
Response::builder()
.status(http::StatusCode::INTERNAL_SERVER_ERROR)
.header(SERVER_FN_ERROR_HEADER, path)
.body(err.ser().unwrap_or_else(|_| err.to_string()).into())
.body(err.into())
.unwrap()
}

View File

@@ -1,65 +1,61 @@
use super::Res;
use super::{Res, TryRes};
use crate::error::{
ServerFnError, ServerFnErrorErr, ServerFnErrorSerde, SERVER_FN_ERROR_HEADER,
FromServerFnError, IntoAppError, ServerFnErrorErr, ServerFnErrorWrapper,
SERVER_FN_ERROR_HEADER,
};
use axum::body::Body;
use bytes::Bytes;
use futures::{Stream, StreamExt};
use futures::{Stream, TryStreamExt};
use http::{header, HeaderValue, Response, StatusCode};
use std::{
fmt::{Debug, Display},
str::FromStr,
};
impl<CustErr> Res<CustErr> for Response<Body>
impl<E> TryRes<E> for Response<Body>
where
CustErr: Send + Sync + Debug + FromStr + Display + 'static,
E: Send + Sync + FromServerFnError,
{
fn try_from_string(
content_type: &str,
data: String,
) -> Result<Self, ServerFnError<CustErr>> {
fn try_from_string(content_type: &str, data: String) -> Result<Self, E> {
let builder = http::Response::builder();
builder
.status(200)
.header(http::header::CONTENT_TYPE, content_type)
.body(Body::from(data))
.map_err(|e| ServerFnError::Response(e.to_string()))
.map_err(|e| {
ServerFnErrorErr::Response(e.to_string()).into_app_error()
})
}
fn try_from_bytes(
content_type: &str,
data: Bytes,
) -> Result<Self, ServerFnError<CustErr>> {
fn try_from_bytes(content_type: &str, data: Bytes) -> Result<Self, E> {
let builder = http::Response::builder();
builder
.status(200)
.header(http::header::CONTENT_TYPE, content_type)
.body(Body::from(data))
.map_err(|e| ServerFnError::Response(e.to_string()))
.map_err(|e| {
ServerFnErrorErr::Response(e.to_string()).into_app_error()
})
}
fn try_from_stream(
content_type: &str,
data: impl Stream<Item = Result<Bytes, ServerFnError<CustErr>>>
+ Send
+ 'static,
) -> Result<Self, ServerFnError<CustErr>> {
let body =
Body::from_stream(data.map(|n| n.map_err(ServerFnErrorErr::from)));
data: impl Stream<Item = Result<Bytes, E>> + Send + 'static,
) -> Result<Self, E> {
let body = Body::from_stream(data.map_err(|e| ServerFnErrorWrapper(e)));
let builder = http::Response::builder();
builder
.status(200)
.header(http::header::CONTENT_TYPE, content_type)
.body(body)
.map_err(|e| ServerFnError::Response(e.to_string()))
.map_err(|e| {
ServerFnErrorErr::Response(e.to_string()).into_app_error()
})
}
}
fn error_response(path: &str, err: &ServerFnError<CustErr>) -> Self {
impl Res for Response<Body> {
fn error_response(path: &str, err: String) -> Self {
Response::builder()
.status(http::StatusCode::INTERNAL_SERVER_ERROR)
.header(SERVER_FN_ERROR_HEADER, path)
.body(err.ser().unwrap_or_else(|_| err.to_string()).into())
.body(err.into())
.unwrap()
}

View File

@@ -13,62 +13,49 @@ pub mod http;
#[cfg(feature = "reqwest")]
pub mod reqwest;
use crate::error::ServerFnError;
use bytes::Bytes;
use futures::Stream;
use std::future::Future;
/// Represents the response as created by the server;
pub trait Res<CustErr>
pub trait TryRes<E>
where
Self: Sized,
{
/// Attempts to convert a UTF-8 string into an HTTP response.
fn try_from_string(
content_type: &str,
data: String,
) -> Result<Self, ServerFnError<CustErr>>;
fn try_from_string(content_type: &str, data: String) -> Result<Self, E>;
/// Attempts to convert a binary blob represented as bytes into an HTTP response.
fn try_from_bytes(
content_type: &str,
data: Bytes,
) -> Result<Self, ServerFnError<CustErr>>;
fn try_from_bytes(content_type: &str, data: Bytes) -> Result<Self, E>;
/// Attempts to convert a stream of bytes into an HTTP response.
fn try_from_stream(
content_type: &str,
data: impl Stream<Item = Result<Bytes, ServerFnError<CustErr>>>
+ Send
+ 'static,
) -> Result<Self, ServerFnError<CustErr>>;
data: impl Stream<Item = Result<Bytes, E>> + 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: &ServerFnError<CustErr>) -> Self;
fn error_response(path: &str, err: String) -> Self;
/// Redirect the response by setting a 302 code and Location header.
fn redirect(&mut self, path: &str);
}
/// Represents the response as received by the client.
pub trait ClientRes<CustErr> {
pub trait ClientRes<E> {
/// Attempts to extract a UTF-8 string from an HTTP response.
fn try_into_string(
self,
) -> impl Future<Output = Result<String, ServerFnError<CustErr>>> + Send;
fn try_into_string(self) -> impl Future<Output = Result<String, E>> + Send;
/// Attempts to extract a binary blob from an HTTP response.
fn try_into_bytes(
self,
) -> impl Future<Output = Result<Bytes, ServerFnError<CustErr>>> + Send;
fn try_into_bytes(self) -> impl Future<Output = Result<Bytes, E>> + Send;
/// Attempts to extract a binary stream from an HTTP response.
fn try_into_stream(
self,
) -> Result<
impl Stream<Item = Result<Bytes, ServerFnError>> + Send + Sync + 'static,
ServerFnError<CustErr>,
>;
) -> Result<impl Stream<Item = Result<Bytes, E>> + Send + Sync + 'static, E>;
/// HTTP status code of the response.
fn status(&self) -> u16;
@@ -91,29 +78,25 @@ pub trait ClientRes<CustErr> {
/// server response type when compiling for the client.
pub struct BrowserMockRes;
impl<CustErr> Res<CustErr> for BrowserMockRes {
fn try_from_string(
_content_type: &str,
_data: String,
) -> Result<Self, ServerFnError<CustErr>> {
impl<E> TryRes<E> for BrowserMockRes {
fn try_from_string(_content_type: &str, _data: String) -> Result<Self, E> {
unreachable!()
}
fn try_from_bytes(
_content_type: &str,
_data: Bytes,
) -> Result<Self, ServerFnError<CustErr>> {
unreachable!()
}
fn error_response(_path: &str, _err: &ServerFnError<CustErr>) -> Self {
fn try_from_bytes(_content_type: &str, _data: Bytes) -> Result<Self, E> {
unreachable!()
}
fn try_from_stream(
_content_type: &str,
_data: impl Stream<Item = Result<Bytes, ServerFnError<CustErr>>>,
) -> Result<Self, ServerFnError<CustErr>> {
_data: impl Stream<Item = Result<Bytes, E>>,
) -> Result<Self, E> {
unreachable!()
}
}
impl Res for BrowserMockRes {
fn error_response(_path: &str, _err: String) -> Self {
unreachable!()
}

View File

@@ -1,31 +1,28 @@
use super::ClientRes;
use crate::error::ServerFnError;
use crate::error::{FromServerFnError, IntoAppError, ServerFnErrorErr};
use bytes::Bytes;
use futures::{Stream, TryStreamExt};
use reqwest::Response;
impl<CustErr> ClientRes<CustErr> for Response {
async fn try_into_string(self) -> Result<String, ServerFnError<CustErr>> {
self.text()
.await
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
impl<E: FromServerFnError> ClientRes<E> for Response {
async fn try_into_string(self) -> Result<String, E> {
self.text().await.map_err(|e| {
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()
})
}
async fn try_into_bytes(self) -> Result<Bytes, ServerFnError<CustErr>> {
self.bytes()
.await
.map_err(|e| ServerFnError::Deserialization(e.to_string()))
async fn try_into_bytes(self) -> Result<Bytes, E> {
self.bytes().await.map_err(|e| {
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()
})
}
fn try_into_stream(
self,
) -> Result<
impl Stream<Item = Result<Bytes, ServerFnError>> + Send + 'static,
ServerFnError<CustErr>,
> {
Ok(self
.bytes_stream()
.map_err(|e| ServerFnError::Response(e.to_string())))
) -> Result<impl Stream<Item = Result<Bytes, E>> + Send + 'static, E> {
Ok(self.bytes_stream().map_err(|e| {
ServerFnErrorErr::Response(e.to_string()).into_app_error()
}))
}
fn status(&self) -> u16 {

View File

@@ -382,13 +382,8 @@ pub fn server_macro_impl(
quote! {
#server_fn_path::inventory::submit! {{
use #server_fn_path::{ServerFn, codec::Encoding};
#server_fn_path::ServerFnTraitObj::new(
#wrapped_struct_name_turbofish::PATH,
<#wrapped_struct_name as ServerFn>::InputEncoding::METHOD,
|req| {
Box::pin(#wrapped_struct_name_turbofish::run_on_server(req))
},
#wrapped_struct_name_turbofish::middlewares
#server_fn_path::ServerFnTraitObj::new::<#wrapped_struct_name>(
|req| Box::pin(#wrapped_struct_name_turbofish::run_on_server(req)),
)
}}
}
@@ -730,12 +725,12 @@ fn output_type(return_ty: &Type) -> Result<&GenericArgument> {
Err(syn::Error::new(
return_ty.span(),
"server functions should return Result<T, ServerFnError> or Result<T, \
ServerFnError<E>>",
"server functions should return Result<T, E> where E: \
FromServerFnError",
))
}
fn err_type(return_ty: &Type) -> Result<Option<&GenericArgument>> {
fn err_type(return_ty: &Type) -> Result<Option<&Type>> {
if let syn::Type::Path(pat) = &return_ty {
if pat.path.segments[0].ident == "Result" {
if let PathArguments::AngleBracketed(args) =
@@ -746,25 +741,8 @@ fn err_type(return_ty: &Type) -> Result<Option<&GenericArgument>> {
return Ok(None);
}
// Result<T, _>
else if let GenericArgument::Type(Type::Path(pat)) =
&args.args[1]
{
if let Some(segment) = pat.path.segments.last() {
if segment.ident == "ServerFnError" {
let args = &segment.arguments;
match args {
// Result<T, ServerFnError>
PathArguments::None => return Ok(None),
// Result<T, ServerFnError<E>>
PathArguments::AngleBracketed(args) => {
if args.args.len() == 1 {
return Ok(Some(&args.args[0]));
}
}
_ => {}
}
}
}
else if let GenericArgument::Type(ty) = &args.args[1] {
return Ok(Some(ty));
}
}
}
@@ -772,8 +750,8 @@ fn err_type(return_ty: &Type) -> Result<Option<&GenericArgument>> {
Err(syn::Error::new(
return_ty.span(),
"server functions should return Result<T, ServerFnError> or Result<T, \
ServerFnError<E>>",
"server functions should return Result<T, E> where E: \
FromServerFnError",
))
}