feat: add support for more HTTP methods in server fn codecs (#3797)

This commit is contained in:
ChosunOne
2025-03-29 18:55:08 +00:00
committed by GitHub
parent c204df2ff3
commit 4c6c79da40
16 changed files with 878 additions and 137 deletions

View File

@@ -1,4 +1,4 @@
use super::Post;
use super::{Patch, Post, Put};
use crate::{ContentType, Decodes, Encodes};
use bytes::Bytes;
use serde::{de::DeserializeOwned, Serialize};
@@ -36,3 +36,13 @@ where
/// Pass arguments and receive responses using `cbor` in a `POST` request.
pub type Cbor = Post<CborEncoding>;
/// Pass arguments and receive responses using `cbor` in the body of a `PATCH` request.
/// **Note**: Browser support for `PATCH` requests without JS/WASM may be poor.
/// Consider using a `POST` request if functionality without JS/WASM is required.
pub type PatchCbor = Patch<CborEncoding>;
/// Pass arguments and receive responses using `cbor` in the body of a `PUT` request.
/// **Note**: Browser support for `PUT` requests without JS/WASM may be poor.
/// Consider using a `POST` request if functionality without JS/WASM is required.
pub type PutCbor = Put<CborEncoding>;

View File

@@ -1,4 +1,4 @@
use super::Post;
use super::{Patch, Post, Put};
use crate::{ContentType, Decodes, Encodes};
use bytes::Bytes;
use serde::{de::DeserializeOwned, Serialize};
@@ -34,3 +34,13 @@ where
/// Pass arguments and receive responses as JSON in the body of a `POST` request.
pub type Json = Post<JsonEncoding>;
/// Pass arguments and receive responses as JSON in the body of a `PATCH` request.
/// **Note**: Browser support for `PATCH` requests without JS/WASM may be poor.
/// Consider using a `POST` request if functionality without JS/WASM is required.
pub type PatchJson = Patch<JsonEncoding>;
/// Pass arguments and receive responses as JSON in the body of a `PUT` request.
/// **Note**: Browser support for `PUT` requests without JS/WASM may be poor.
/// Consider using a `POST` request if functionality without JS/WASM is required.
pub type PutJson = Put<JsonEncoding>;

View File

@@ -50,8 +50,12 @@ mod postcard;
#[cfg(feature = "postcard")]
pub use postcard::*;
mod patch;
pub use patch::*;
mod post;
pub use post::*;
mod put;
pub use put::*;
mod stream;
use crate::ContentType;
use futures::Future;

View File

@@ -1,4 +1,7 @@
use crate::{codec::Post, ContentType, Decodes, Encodes};
use crate::{
codec::{Patch, Post, Put},
ContentType, Decodes, Encodes,
};
use bytes::Bytes;
use serde::{de::DeserializeOwned, Serialize};
@@ -33,3 +36,13 @@ where
/// Pass arguments and receive responses as MessagePack in a `POST` request.
pub type MsgPack = Post<MsgPackEncoding>;
/// Pass arguments and receive responses as MessagePack in the body of a `PATCH` request.
/// **Note**: Browser support for `PATCH` requests without JS/WASM may be poor.
/// Consider using a `POST` request if functionality without JS/WASM is required.
pub type PatchMsgPack = Patch<MsgPackEncoding>;
/// Pass arguments and receive responses as MessagePack in the body of a `PUT` request.
/// **Note**: Browser support for `PUT` requests without JS/WASM may be poor.
/// Consider using a `POST` request if functionality without JS/WASM is required.
pub type PutMsgPack = Put<MsgPackEncoding>;

View File

@@ -66,7 +66,7 @@ where
{
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
let multi = self.into();
Request::try_new_multipart(
Request::try_new_post_multipart(
path,
accepts,
multi.into_client_data().unwrap(),

View File

@@ -0,0 +1,83 @@
use super::{Encoding, FromReq, FromRes, IntoReq, IntoRes};
use crate::{
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
request::{ClientReq, Req},
response::{ClientRes, TryRes},
ContentType, Decodes, Encodes,
};
use std::marker::PhantomData;
/// A codec that encodes the data in the patch body
pub struct Patch<Codec>(PhantomData<Codec>);
impl<Codec: ContentType> ContentType for Patch<Codec> {
const CONTENT_TYPE: &'static str = Codec::CONTENT_TYPE;
}
impl<Codec: ContentType> Encoding for Patch<Codec> {
const METHOD: http::Method = http::Method::PATCH;
}
impl<E, T, Encoding, Request> IntoReq<Patch<Encoding>, Request, E> for T
where
Request: ClientReq<E>,
Encoding: Encodes<T>,
E: FromServerFnError,
{
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
let data = Encoding::encode(self).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Request::try_new_patch_bytes(
path,
accepts,
Encoding::CONTENT_TYPE,
data,
)
}
}
impl<E, T, Request, Encoding> FromReq<Patch<Encoding>, Request, E> for T
where
Request: Req<E> + Send + 'static,
Encoding: Decodes<T>,
E: FromServerFnError,
{
async fn from_req(req: Request) -> Result<Self, E> {
let data = req.try_into_bytes().await?;
let s = Encoding::decode(data).map_err(|e| {
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()
})?;
Ok(s)
}
}
impl<E, Response, Encoding, T> IntoRes<Patch<Encoding>, Response, E> for T
where
Response: TryRes<E>,
Encoding: Encodes<T>,
E: FromServerFnError + Send,
T: Send,
{
async fn into_res(self) -> Result<Response, E> {
let data = Encoding::encode(self).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Response::try_from_bytes(Encoding::CONTENT_TYPE, data)
}
}
impl<E, Encoding, Response, T> FromRes<Patch<Encoding>, Response, E> for T
where
Response: ClientRes<E> + Send,
Encoding: Decodes<T>,
E: FromServerFnError,
{
async fn from_res(res: Response) -> Result<Self, E> {
let data = res.try_into_bytes().await?;
let s = Encoding::decode(data).map_err(|e| {
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()
})?;
Ok(s)
}
}

View File

@@ -1,4 +1,7 @@
use crate::{codec::Post, ContentType, Decodes, Encodes};
use crate::{
codec::{Patch, Post, Put},
ContentType, Decodes, Encodes,
};
use bytes::Bytes;
use serde::{de::DeserializeOwned, Serialize};
@@ -33,3 +36,13 @@ where
/// Pass arguments and receive responses with postcard in a `POST` request.
pub type Postcard = Post<PostcardEncoding>;
/// Pass arguments and receive responses with postcard in a `PATCH` request.
/// **Note**: Browser support for `PATCH` requests without JS/WASM may be poor.
/// Consider using a `POST` request if functionality without JS/WASM is required.
pub type PatchPostcard = Patch<PostcardEncoding>;
/// Pass arguments and receive responses with postcard in a `PUT` request.
/// **Note**: Browser support for `PUT` requests without JS/WASM may be poor.
/// Consider using a `POST` request if functionality without JS/WASM is required.
pub type PutPostcard = Put<PostcardEncoding>;

View File

@@ -0,0 +1,78 @@
use super::{Encoding, FromReq, FromRes, IntoReq, IntoRes};
use crate::{
error::{FromServerFnError, IntoAppError, ServerFnErrorErr},
request::{ClientReq, Req},
response::{ClientRes, TryRes},
ContentType, Decodes, Encodes,
};
use std::marker::PhantomData;
/// A codec that encodes the data in the put body
pub struct Put<Codec>(PhantomData<Codec>);
impl<Codec: ContentType> ContentType for Put<Codec> {
const CONTENT_TYPE: &'static str = Codec::CONTENT_TYPE;
}
impl<Codec: ContentType> Encoding for Put<Codec> {
const METHOD: http::Method = http::Method::PUT;
}
impl<E, T, Encoding, Request> IntoReq<Put<Encoding>, Request, E> for T
where
Request: ClientReq<E>,
Encoding: Encodes<T>,
E: FromServerFnError,
{
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
let data = Encoding::encode(self).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Request::try_new_put_bytes(path, accepts, Encoding::CONTENT_TYPE, data)
}
}
impl<E, T, Request, Encoding> FromReq<Put<Encoding>, Request, E> for T
where
Request: Req<E> + Send + 'static,
Encoding: Decodes<T>,
E: FromServerFnError,
{
async fn from_req(req: Request) -> Result<Self, E> {
let data = req.try_into_bytes().await?;
let s = Encoding::decode(data).map_err(|e| {
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()
})?;
Ok(s)
}
}
impl<E, Response, Encoding, T> IntoRes<Put<Encoding>, Response, E> for T
where
Response: TryRes<E>,
Encoding: Encodes<T>,
E: FromServerFnError + Send,
T: Send,
{
async fn into_res(self) -> Result<Response, E> {
let data = Encoding::encode(self).map_err(|e| {
ServerFnErrorErr::Serialization(e.to_string()).into_app_error()
})?;
Response::try_from_bytes(Encoding::CONTENT_TYPE, data)
}
}
impl<E, Encoding, Response, T> FromRes<Put<Encoding>, Response, E> for T
where
Response: ClientRes<E> + Send,
Encoding: Decodes<T>,
E: FromServerFnError,
{
async fn from_res(res: Response) -> Result<Self, E> {
let data = res.try_into_bytes().await?;
let s = Encoding::decode(data).map_err(|e| {
ServerFnErrorErr::Deserialization(e.to_string()).into_app_error()
})?;
Ok(s)
}
}

View File

@@ -1,4 +1,7 @@
use crate::{codec::Post, ContentType, Decodes, Encodes};
use crate::{
codec::{Patch, Post, Put},
ContentType, Decodes, Encodes,
};
use bytes::Bytes;
use rkyv::{
api::high::{HighDeserializer, HighSerializer, HighValidator},
@@ -52,3 +55,13 @@ where
/// Pass arguments and receive responses as `rkyv` in a `POST` request.
pub type Rkyv = Post<RkyvEncoding>;
/// Pass arguments and receive responses as `rkyv` in a `PATCH` request.
/// **Note**: Browser support for `PATCH` requests without JS/WASM may be poor.
/// Consider using a `POST` request if functionality without JS/WASM is required.
pub type PatchRkyv = Patch<RkyvEncoding>;
/// Pass arguments and receive responses as `rkyv` in a `PUT` request.
/// **Note**: Browser support for `PUT` requests without JS/WASM may be poor.
/// Consider using a `POST` request if functionality without JS/WASM is required.
pub type PutRkyv = Put<RkyvEncoding>;

View File

@@ -1,5 +1,7 @@
use crate::{
codec::Post, error::ServerFnErrorErr, ContentType, Decodes, Encodes,
codec::{Patch, Post, Put},
error::ServerFnErrorErr,
ContentType, Decodes, Encodes,
};
use bytes::Bytes;
use serde_lite::{Deserialize, Serialize};
@@ -46,3 +48,13 @@ where
/// Pass arguments and receive responses as JSON in the body of a `POST` request.
pub type SerdeLite = Post<SerdeLiteEncoding>;
/// Pass arguments and receive responses as JSON in the body of a `PATCH` request.
/// **Note**: Browser support for `PATCH` requests without JS/WASM may be poor.
/// Consider using a `POST` request if functionality without JS/WASM is required.
pub type PatchSerdeLite = Patch<SerdeLiteEncoding>;
/// Pass arguments and receive responses as JSON in the body of a `PUT` request.
/// **Note**: Browser support for `PUT` requests without JS/WASM may be poor.
/// Consider using a `POST` request if functionality without JS/WASM is required.
pub type PutSerdeLite = Put<SerdeLiteEncoding>;

View File

@@ -38,7 +38,12 @@ where
T: Stream<Item = Bytes> + Send + Sync + 'static,
{
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
Request::try_new_streaming(path, accepts, Streaming::CONTENT_TYPE, self)
Request::try_new_post_streaming(
path,
accepts,
Streaming::CONTENT_TYPE,
self,
)
}
}
@@ -201,7 +206,7 @@ where
{
fn into_req(self, path: &str, accepts: &str) -> Result<Request, E> {
let data = self.into();
Request::try_new_streaming(
Request::try_new_post_streaming(
path,
accepts,
Streaming::CONTENT_TYPE,

View File

@@ -13,6 +13,21 @@ pub struct GetUrl;
/// Pass arguments as the URL-encoded body of a `POST` request.
pub struct PostUrl;
/// Pass arguments as the URL-encoded body of a `DELETE` request.
/// **Note**: Browser support for `DELETE` requests without JS/WASM may be poor.
/// Consider using a `POST` request if functionality without JS/WASM is required.
pub struct DeleteUrl;
/// Pass arguments as the URL-encoded body of a `PATCH` request.
/// **Note**: Browser support for `PATCH` requests without JS/WASM may be poor.
/// Consider using a `POST` request if functionality without JS/WASM is required.
pub struct PatchUrl;
/// Pass arguments as the URL-encoded body of a `PUT` request.
/// **Note**: Browser support for `PUT` requests without JS/WASM may be poor.
/// Consider using a `POST` request if functionality without JS/WASM is required.
pub struct PutUrl;
impl ContentType for GetUrl {
const CONTENT_TYPE: &'static str = "application/x-www-form-urlencoded";
}
@@ -90,3 +105,120 @@ where
Ok(args)
}
}
impl ContentType for DeleteUrl {
const CONTENT_TYPE: &'static str = "application/x-www-form-urlencoded";
}
impl Encoding for DeleteUrl {
const METHOD: Method = Method::DELETE;
}
impl<E, T, Request> IntoReq<DeleteUrl, Request, E> for T
where
Request: ClientReq<E>,
T: Serialize + Send,
E: FromServerFnError,
{
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_delete(path, accepts, GetUrl::CONTENT_TYPE, &data)
}
}
impl<E, T, Request> FromReq<DeleteUrl, Request, E> for T
where
Request: Req<E> + Send + 'static,
T: DeserializeOwned,
E: FromServerFnError,
{
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| {
ServerFnErrorErr::Args(e.to_string()).into_app_error()
})?;
Ok(args)
}
}
impl ContentType for PatchUrl {
const CONTENT_TYPE: &'static str = "application/x-www-form-urlencoded";
}
impl Encoding for PatchUrl {
const METHOD: Method = Method::PATCH;
}
impl<E, T, Request> IntoReq<PatchUrl, Request, E> for T
where
Request: ClientReq<E>,
T: Serialize + Send,
E: FromServerFnError,
{
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_patch(path, accepts, GetUrl::CONTENT_TYPE, data)
}
}
impl<E, T, Request> FromReq<PatchUrl, Request, E> for T
where
Request: Req<E> + Send + 'static,
T: DeserializeOwned,
E: FromServerFnError,
{
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| {
ServerFnErrorErr::Args(e.to_string()).into_app_error()
})?;
Ok(args)
}
}
impl ContentType for PutUrl {
const CONTENT_TYPE: &'static str = "application/x-www-form-urlencoded";
}
impl Encoding for PutUrl {
const METHOD: Method = Method::PUT;
}
impl<E, T, Request> IntoReq<PutUrl, Request, E> for T
where
Request: ClientReq<E>,
T: Serialize + Send,
E: FromServerFnError,
{
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_put(path, accepts, GetUrl::CONTENT_TYPE, data)
}
}
impl<E, T, Request> FromReq<PutUrl, Request, E> for T
where
Request: Req<E> + Send + 'static,
T: DeserializeOwned,
E: FromServerFnError,
{
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| {
ServerFnErrorErr::Args(e.to_string()).into_app_error()
})?;
Ok(args)
}
}

View File

@@ -282,6 +282,9 @@ where
ServerFnError::MissingArg(value)
}
ServerFnErrorErr::Response(value) => ServerFnError::Response(value),
ServerFnErrorErr::UnsupportedRequestMethod(value) => {
ServerFnError::Request(value)
}
}
}
@@ -377,6 +380,9 @@ pub enum ServerFnErrorErr {
/// Error while trying to register the server function (only occurs in case of poisoned RwLock).
#[error("error while trying to register the server function: {0}")]
Registration(String),
/// Occurs on the client if trying to use an unsupported `HTTP` method when building a request.
#[error("error trying to build `HTTP` method request: {0}")]
UnsupportedRequestMethod(String),
/// Occurs on the client if there is a network error while trying to run function on server.
#[error("error reaching server to call server function: {0}")]
Request(String),

View File

@@ -6,6 +6,7 @@ use crate::{
use bytes::Bytes;
use futures::{Stream, StreamExt};
pub use gloo_net::http::Request;
use http::Method;
use js_sys::{Reflect, Uint8Array};
use send_wrapper::SendWrapper;
use std::ops::{Deref, DerefMut};
@@ -92,11 +93,12 @@ where
{
type FormData = BrowserFormData;
fn try_new_get(
fn try_new_req_query(
path: &str,
accepts: &str,
content_type: &str,
accepts: &str,
query: &str,
method: http::Method,
) -> Result<Self, E> {
let (abort_ctrl, abort_signal) = abort_signal();
let server_url = get_server_url();
@@ -108,25 +110,39 @@ where
url.push('?');
url.push_str(query);
Ok(Self(SendWrapper::new(RequestInner {
request: Request::get(&url)
.header("Content-Type", content_type)
.header("Accept", accepts)
.abort_signal(abort_signal.as_ref())
.build()
.map_err(|e| {
E::from_server_fn_error(ServerFnErrorErr::Request(
e.to_string(),
request: match method {
Method::GET => Request::get(&url),
Method::DELETE => Request::delete(&url),
Method::POST => Request::post(&url),
Method::PUT => Request::put(&url),
Method::PATCH => Request::patch(&url),
m => {
return Err(E::from_server_fn_error(
ServerFnErrorErr::UnsupportedRequestMethod(
m.to_string(),
),
))
})?,
}
}
.header("Content-Type", content_type)
.header("Accept", accepts)
.abort_signal(abort_signal.as_ref())
.build()
.map_err(|e| {
E::from_server_fn_error(ServerFnErrorErr::Request(
e.to_string(),
))
})?,
abort_ctrl,
})))
}
fn try_new_post(
fn try_new_req_text(
path: &str,
accepts: &str,
content_type: &str,
accepts: &str,
body: String,
method: Method,
) -> Result<Self, E> {
let (abort_ctrl, abort_signal) = abort_signal();
let server_url = get_server_url();
@@ -134,25 +150,37 @@ where
url.push_str(server_url);
url.push_str(path);
Ok(Self(SendWrapper::new(RequestInner {
request: Request::post(&url)
.header("Content-Type", content_type)
.header("Accept", accepts)
.abort_signal(abort_signal.as_ref())
.body(body)
.map_err(|e| {
E::from_server_fn_error(ServerFnErrorErr::Request(
e.to_string(),
request: match method {
Method::POST => Request::post(&url),
Method::PATCH => Request::patch(&url),
Method::PUT => Request::put(&url),
m => {
return Err(E::from_server_fn_error(
ServerFnErrorErr::UnsupportedRequestMethod(
m.to_string(),
),
))
})?,
}
}
.header("Content-Type", content_type)
.header("Accept", accepts)
.abort_signal(abort_signal.as_ref())
.body(body)
.map_err(|e| {
E::from_server_fn_error(ServerFnErrorErr::Request(
e.to_string(),
))
})?,
abort_ctrl,
})))
}
fn try_new_post_bytes(
fn try_new_req_bytes(
path: &str,
accepts: &str,
content_type: &str,
accepts: &str,
body: Bytes,
method: Method,
) -> Result<Self, E> {
let (abort_ctrl, abort_signal) = abort_signal();
let server_url = get_server_url();
@@ -162,24 +190,36 @@ where
let body: &[u8] = &body;
let body = Uint8Array::from(body).buffer();
Ok(Self(SendWrapper::new(RequestInner {
request: Request::post(&url)
.header("Content-Type", content_type)
.header("Accept", accepts)
.abort_signal(abort_signal.as_ref())
.body(body)
.map_err(|e| {
E::from_server_fn_error(ServerFnErrorErr::Request(
e.to_string(),
request: match method {
Method::POST => Request::post(&url),
Method::PATCH => Request::patch(&url),
Method::PUT => Request::put(&url),
m => {
return Err(E::from_server_fn_error(
ServerFnErrorErr::UnsupportedRequestMethod(
m.to_string(),
),
))
})?,
}
}
.header("Content-Type", content_type)
.header("Accept", accepts)
.abort_signal(abort_signal.as_ref())
.body(body)
.map_err(|e| {
E::from_server_fn_error(ServerFnErrorErr::Request(
e.to_string(),
))
})?,
abort_ctrl,
})))
}
fn try_new_multipart(
fn try_new_req_multipart(
path: &str,
accepts: &str,
body: Self::FormData,
method: Method,
) -> Result<Self, E> {
let (abort_ctrl, abort_signal) = abort_signal();
let server_url = get_server_url();
@@ -187,24 +227,36 @@ where
url.push_str(server_url);
url.push_str(path);
Ok(Self(SendWrapper::new(RequestInner {
request: Request::post(&url)
.header("Accept", accepts)
.abort_signal(abort_signal.as_ref())
.body(body.0.take())
.map_err(|e| {
E::from_server_fn_error(ServerFnErrorErr::Request(
e.to_string(),
request: match method {
Method::POST => Request::post(&url),
Method::PATCH => Request::patch(&url),
Method::PUT => Request::put(&url),
m => {
return Err(E::from_server_fn_error(
ServerFnErrorErr::UnsupportedRequestMethod(
m.to_string(),
),
))
})?,
}
}
.header("Accept", accepts)
.abort_signal(abort_signal.as_ref())
.body(body.0.take())
.map_err(|e| {
E::from_server_fn_error(ServerFnErrorErr::Request(
e.to_string(),
))
})?,
abort_ctrl,
})))
}
fn try_new_post_form_data(
fn try_new_req_form_data(
path: &str,
accepts: &str,
content_type: &str,
body: Self::FormData,
method: Method,
) -> Result<Self, E> {
let (abort_ctrl, abort_signal) = abort_signal();
let form_data = body.0.take();
@@ -219,35 +271,55 @@ where
))
})?;
Ok(Self(SendWrapper::new(RequestInner {
request: Request::post(path)
.header("Content-Type", content_type)
.header("Accept", accepts)
.abort_signal(abort_signal.as_ref())
.body(url_params)
.map_err(|e| {
E::from_server_fn_error(ServerFnErrorErr::Request(
e.to_string(),
request: match method {
Method::POST => Request::post(path),
Method::PUT => Request::put(path),
Method::PATCH => Request::patch(path),
m => {
return Err(E::from_server_fn_error(
ServerFnErrorErr::UnsupportedRequestMethod(
m.to_string(),
),
))
})?,
}
}
.header("Content-Type", content_type)
.header("Accept", accepts)
.abort_signal(abort_signal.as_ref())
.body(url_params)
.map_err(|e| {
E::from_server_fn_error(ServerFnErrorErr::Request(
e.to_string(),
))
})?,
abort_ctrl,
})))
}
fn try_new_streaming(
fn try_new_req_streaming(
path: &str,
accepts: &str,
content_type: &str,
body: impl Stream<Item = Bytes> + 'static,
method: Method,
) -> Result<Self, E> {
// Only allow for methods with bodies
match method {
Method::POST | Method::PATCH | Method::PUT => {}
m => {
return Err(E::from_server_fn_error(
ServerFnErrorErr::UnsupportedRequestMethod(m.to_string()),
))
}
}
// TODO abort signal
let (request, abort_ctrl) =
streaming_request(path, accepts, content_type, body).map_err(
|e| {
streaming_request(path, accepts, content_type, body, method)
.map_err(|e| {
E::from_server_fn_error(ServerFnErrorErr::Request(format!(
"{e:?}"
)))
},
)?;
})?;
Ok(Self(SendWrapper::new(RequestInner {
request,
abort_ctrl,
@@ -260,6 +332,7 @@ fn streaming_request(
accepts: &str,
content_type: &str,
body: impl Stream<Item = Bytes> + 'static,
method: Method,
) -> Result<(Request, Option<AbortOnDrop>), JsValue> {
let (abort_ctrl, abort_signal) = abort_signal();
let stream = ReadableStream::from_stream(body.map(|bytes| {
@@ -275,7 +348,7 @@ fn streaming_request(
let init = RequestInit::new();
init.set_headers(&headers);
init.set_method("POST");
init.set_method(method.as_str());
init.set_signal(abort_signal.as_ref());
init.set_body(&stream);

View File

@@ -1,5 +1,6 @@
use bytes::Bytes;
use futures::{Sink, Stream};
use http::Method;
use std::{borrow::Cow, future::Future};
/// Request types for Actix.
@@ -25,13 +26,86 @@ where
/// The type used for URL-encoded form data in this client.
type FormData;
/// Attempts to construct a new request with query parameters.
fn try_new_req_query(
path: &str,
content_type: &str,
accepts: &str,
query: &str,
method: Method,
) -> Result<Self, E>;
/// Attempts to construct a new request with a text body.
fn try_new_req_text(
path: &str,
content_type: &str,
accepts: &str,
body: String,
method: Method,
) -> Result<Self, E>;
/// Attempts to construct a new request with a binary body.
fn try_new_req_bytes(
path: &str,
content_type: &str,
accepts: &str,
body: Bytes,
method: Method,
) -> Result<Self, E>;
/// Attempts to construct a new request with form data as the body.
fn try_new_req_form_data(
path: &str,
accepts: &str,
content_type: &str,
body: Self::FormData,
method: Method,
) -> Result<Self, E>;
/// Attempts to construct a new request with a multipart body.
fn try_new_req_multipart(
path: &str,
accepts: &str,
body: Self::FormData,
method: Method,
) -> Result<Self, E>;
/// Attempts to construct a new request with a streaming body.
fn try_new_req_streaming(
path: &str,
accepts: &str,
content_type: &str,
body: impl Stream<Item = Bytes> + Send + 'static,
method: Method,
) -> Result<Self, E>;
/// Attempts to construct a new `GET` request.
fn try_new_get(
path: &str,
content_type: &str,
accepts: &str,
query: &str,
) -> Result<Self, E>;
) -> Result<Self, E> {
Self::try_new_req_query(path, content_type, accepts, query, Method::GET)
}
/// Attempts to construct a new `DELETE` request.
/// **Note**: Browser support for `DELETE` requests without JS/WASM may be poor.
/// Consider using a `POST` request if functionality without JS/WASM is required.
fn try_new_delete(
path: &str,
content_type: &str,
accepts: &str,
query: &str,
) -> Result<Self, E> {
Self::try_new_req_query(
path,
content_type,
accepts,
query,
Method::DELETE,
)
}
/// Attempts to construct a new `POST` request with a text body.
fn try_new_post(
@@ -39,7 +113,33 @@ where
content_type: &str,
accepts: &str,
body: String,
) -> Result<Self, E>;
) -> Result<Self, E> {
Self::try_new_req_text(path, content_type, accepts, body, Method::POST)
}
/// Attempts to construct a new `PATCH` request with a text body.
/// **Note**: Browser support for `PATCH` requests without JS/WASM may be poor.
/// Consider using a `POST` request if functionality without JS/WASM is required.
fn try_new_patch(
path: &str,
content_type: &str,
accepts: &str,
body: String,
) -> Result<Self, E> {
Self::try_new_req_text(path, content_type, accepts, body, Method::PATCH)
}
/// Attempts to construct a new `PUT` request with a text body.
/// **Note**: Browser support for `PUT` requests without JS/WASM may be poor.
/// Consider using a `POST` request if functionality without JS/WASM is required.
fn try_new_put(
path: &str,
content_type: &str,
accepts: &str,
body: String,
) -> Result<Self, E> {
Self::try_new_req_text(path, content_type, accepts, body, Method::PUT)
}
/// Attempts to construct a new `POST` request with a binary body.
fn try_new_post_bytes(
@@ -47,7 +147,39 @@ where
content_type: &str,
accepts: &str,
body: Bytes,
) -> Result<Self, E>;
) -> Result<Self, E> {
Self::try_new_req_bytes(path, content_type, accepts, body, Method::POST)
}
/// Attempts to construct a new `PATCH` request with a binary body.
/// **Note**: Browser support for `PATCH` requests without JS/WASM may be poor.
/// Consider using a `POST` request if functionality without JS/WASM is required.
fn try_new_patch_bytes(
path: &str,
content_type: &str,
accepts: &str,
body: Bytes,
) -> Result<Self, E> {
Self::try_new_req_bytes(
path,
content_type,
accepts,
body,
Method::PATCH,
)
}
/// Attempts to construct a new `PUT` request with a binary body.
/// **Note**: Browser support for `PUT` requests without JS/WASM may be poor.
/// Consider using a `POST` request if functionality without JS/WASM is required.
fn try_new_put_bytes(
path: &str,
content_type: &str,
accepts: &str,
body: Bytes,
) -> Result<Self, E> {
Self::try_new_req_bytes(path, content_type, accepts, body, Method::PUT)
}
/// Attempts to construct a new `POST` request with form data as the body.
fn try_new_post_form_data(
@@ -55,22 +187,134 @@ where
accepts: &str,
content_type: &str,
body: Self::FormData,
) -> Result<Self, E>;
) -> Result<Self, E> {
Self::try_new_req_form_data(
path,
accepts,
content_type,
body,
Method::POST,
)
}
/// Attempts to construct a new `PATCH` request with form data as the body.
/// **Note**: Browser support for `PATCH` requests without JS/WASM may be poor.
/// Consider using a `POST` request if functionality without JS/WASM is required.
fn try_new_patch_form_data(
path: &str,
accepts: &str,
content_type: &str,
body: Self::FormData,
) -> Result<Self, E> {
Self::try_new_req_form_data(
path,
accepts,
content_type,
body,
Method::PATCH,
)
}
/// Attempts to construct a new `PUT` request with form data as the body.
/// **Note**: Browser support for `PUT` requests without JS/WASM may be poor.
/// Consider using a `POST` request if functionality without JS/WASM is required.
fn try_new_put_form_data(
path: &str,
accepts: &str,
content_type: &str,
body: Self::FormData,
) -> Result<Self, E> {
Self::try_new_req_form_data(
path,
accepts,
content_type,
body,
Method::PUT,
)
}
/// Attempts to construct a new `POST` request with a multipart body.
fn try_new_multipart(
fn try_new_post_multipart(
path: &str,
accepts: &str,
body: Self::FormData,
) -> Result<Self, E>;
) -> Result<Self, E> {
Self::try_new_req_multipart(path, accepts, body, Method::POST)
}
/// Attempts to construct a new `PATCH` request with a multipart body.
/// **Note**: Browser support for `PATCH` requests without JS/WASM may be poor.
/// Consider using a `POST` request if functionality without JS/WASM is required.
fn try_new_patch_multipart(
path: &str,
accepts: &str,
body: Self::FormData,
) -> Result<Self, E> {
Self::try_new_req_multipart(path, accepts, body, Method::PATCH)
}
/// Attempts to construct a new `PUT` request with a multipart body.
/// **Note**: Browser support for `PUT` requests without JS/WASM may be poor.
/// Consider using a `POST` request if functionality without JS/WASM is required.
fn try_new_put_multipart(
path: &str,
accepts: &str,
body: Self::FormData,
) -> Result<Self, E> {
Self::try_new_req_multipart(path, accepts, body, Method::PUT)
}
/// Attempts to construct a new `POST` request with a streaming body.
fn try_new_streaming(
fn try_new_post_streaming(
path: &str,
accepts: &str,
content_type: &str,
body: impl Stream<Item = Bytes> + Send + 'static,
) -> Result<Self, E>;
) -> Result<Self, E> {
Self::try_new_req_streaming(
path,
accepts,
content_type,
body,
Method::POST,
)
}
/// Attempts to construct a new `PATCH` request with a streaming body.
/// **Note**: Browser support for `PATCH` requests without JS/WASM may be poor.
/// Consider using a `POST` request if functionality without JS/WASM is required.
fn try_new_patch_streaming(
path: &str,
accepts: &str,
content_type: &str,
body: impl Stream<Item = Bytes> + Send + 'static,
) -> Result<Self, E> {
Self::try_new_req_streaming(
path,
accepts,
content_type,
body,
Method::PATCH,
)
}
/// Attempts to construct a new `PUT` request with a streaming body.
/// **Note**: Browser support for `PUT` requests without JS/WASM may be poor.
/// Consider using a `POST` request if functionality without JS/WASM is required.
fn try_new_put_streaming(
path: &str,
accepts: &str,
content_type: &str,
body: impl Stream<Item = Bytes> + Send + 'static,
) -> Result<Self, E> {
Self::try_new_req_streaming(
path,
accepts,
content_type,
body,
Method::PUT,
)
}
}
/// Represents the request as received by the server.

View File

@@ -20,116 +20,161 @@ where
{
type FormData = Form;
fn try_new_get(
fn try_new_req_query(
path: &str,
accepts: &str,
content_type: &str,
accepts: &str,
query: &str,
method: Method,
) -> Result<Self, E> {
let url = format!("{}{}", get_server_url(), path);
let mut url = Url::try_from(url.as_str()).map_err(|e| {
E::from_server_fn_error(ServerFnErrorErr::Request(e.to_string()))
})?;
url.set_query(Some(query));
let req = CLIENT
.get(url)
.header(CONTENT_TYPE, content_type)
.header(ACCEPT, accepts)
.build()
.map_err(|e| {
E::from_server_fn_error(ServerFnErrorErr::Request(
e.to_string(),
let req = match method {
Method::GET => CLIENT.get(url),
Method::DELETE => CLIENT.delete(url),
Method::HEAD => CLIENT.head(url),
Method::POST => CLIENT.post(url),
Method::PATCH => CLIENT.patch(url),
Method::PUT => CLIENT.put(url),
m => {
return Err(E::from_server_fn_error(
ServerFnErrorErr::UnsupportedRequestMethod(m.to_string()),
))
})?;
}
}
.header(CONTENT_TYPE, content_type)
.header(ACCEPT, accepts)
.build()
.map_err(|e| {
E::from_server_fn_error(ServerFnErrorErr::Request(e.to_string()))
})?;
Ok(req)
}
fn try_new_post(
fn try_new_req_text(
path: &str,
accepts: &str,
content_type: &str,
accepts: &str,
body: String,
method: Method,
) -> Result<Self, E> {
let url = format!("{}{}", get_server_url(), path);
CLIENT
.post(url)
.header(CONTENT_TYPE, content_type)
.header(ACCEPT, accepts)
.body(body)
.build()
.map_err(|e| {
ServerFnErrorErr::Request(e.to_string()).into_app_error()
})
match method {
Method::POST => CLIENT.post(url),
Method::PUT => CLIENT.put(url),
Method::PATCH => CLIENT.patch(url),
m => {
return Err(E::from_server_fn_error(
ServerFnErrorErr::UnsupportedRequestMethod(m.to_string()),
))
}
}
.header(CONTENT_TYPE, content_type)
.header(ACCEPT, accepts)
.body(body)
.build()
.map_err(|e| ServerFnErrorErr::Request(e.to_string()).into_app_error())
}
fn try_new_post_bytes(
fn try_new_req_bytes(
path: &str,
accepts: &str,
content_type: &str,
accepts: &str,
body: Bytes,
method: Method,
) -> Result<Self, E> {
let url = format!("{}{}", get_server_url(), path);
CLIENT
.post(url)
.header(CONTENT_TYPE, content_type)
.header(ACCEPT, accepts)
.body(body)
.build()
.map_err(|e| {
ServerFnErrorErr::Request(e.to_string()).into_app_error()
})
match method {
Method::POST => CLIENT.post(url),
Method::PATCH => CLIENT.patch(url),
Method::PUT => CLIENT.put(url),
m => {
return Err(E::from_server_fn_error(
ServerFnErrorErr::UnsupportedRequestMethod(m.to_string()),
))
}
}
.header(CONTENT_TYPE, content_type)
.header(ACCEPT, accepts)
.body(body)
.build()
.map_err(|e| ServerFnErrorErr::Request(e.to_string()).into_app_error())
}
fn try_new_multipart(
fn try_new_req_multipart(
path: &str,
accepts: &str,
body: Self::FormData,
method: Method,
) -> Result<Self, E> {
CLIENT
.post(path)
.header(ACCEPT, accepts)
.multipart(body)
.build()
.map_err(|e| {
ServerFnErrorErr::Request(e.to_string()).into_app_error()
})
match method {
Method::POST => CLIENT.post(path),
Method::PUT => CLIENT.put(path),
Method::PATCH => CLIENT.patch(path),
m => {
return Err(E::from_server_fn_error(
ServerFnErrorErr::UnsupportedRequestMethod(m.to_string()),
))
}
}
.header(ACCEPT, accepts)
.multipart(body)
.build()
.map_err(|e| ServerFnErrorErr::Request(e.to_string()).into_app_error())
}
fn try_new_post_form_data(
fn try_new_req_form_data(
path: &str,
accepts: &str,
content_type: &str,
body: Self::FormData,
method: Method,
) -> Result<Self, E> {
CLIENT
.post(path)
.header(CONTENT_TYPE, content_type)
.header(ACCEPT, accepts)
.multipart(body)
.build()
.map_err(|e| {
ServerFnErrorErr::Request(e.to_string()).into_app_error()
})
match method {
Method::POST => CLIENT.post(path),
Method::PATCH => CLIENT.patch(path),
Method::PUT => CLIENT.put(path),
m => {
return Err(E::from_server_fn_error(
ServerFnErrorErr::UnsupportedRequestMethod(m.to_string()),
))
}
}
.header(CONTENT_TYPE, content_type)
.header(ACCEPT, accepts)
.multipart(body)
.build()
.map_err(|e| ServerFnErrorErr::Request(e.to_string()).into_app_error())
}
fn try_new_streaming(
fn try_new_req_streaming(
path: &str,
accepts: &str,
content_type: &str,
body: impl Stream<Item = Bytes> + Send + 'static,
method: Method,
) -> Result<Self, E> {
let url = format!("{}{}", get_server_url(), path);
let body = Body::wrap_stream(
body.map(|chunk| Ok(chunk) as Result<Bytes, ServerFnErrorErr>),
);
CLIENT
.post(url)
.header(CONTENT_TYPE, content_type)
.header(ACCEPT, accepts)
.body(body)
.build()
.map_err(|e| {
ServerFnErrorErr::Request(e.to_string()).into_app_error()
})
match method {
Method::POST => CLIENT.post(url),
Method::PUT => CLIENT.put(url),
Method::PATCH => CLIENT.patch(url),
m => {
return Err(E::from_server_fn_error(
ServerFnErrorErr::UnsupportedRequestMethod(m.to_string()),
))
}
}
.header(CONTENT_TYPE, content_type)
.header(ACCEPT, accepts)
.body(body)
.build()
.map_err(|e| ServerFnErrorErr::Request(e.to_string()).into_app_error())
}
}