more docs

This commit is contained in:
Greg Johnston 2024-01-11 22:09:40 -05:00
parent 0a9cdba22e
commit 15b04a8a85
15 changed files with 221 additions and 55 deletions

View file

@ -54,6 +54,7 @@ pub mod browser {
}
#[cfg(any(feature = "reqwest", doc))]
/// Implements [`Client`] for a request made by [`reqwest`].
pub mod reqwest {
use super::Client;
use crate::{error::ServerFnError, request::reqwest::CLIENT};
@ -61,6 +62,7 @@ pub mod reqwest {
use reqwest::{Request, Response};
use std::future::Future;
/// Implements [`Client`] for a request made by [`reqwest`].
pub struct ReqwestClient;
impl<CustErr> Client<CustErr> for ReqwestClient {

View file

@ -45,15 +45,56 @@ mod multipart;
pub use multipart::*;
mod stream;
use crate::{error::ServerFnError, request::ClientReq};
use crate::error::ServerFnError;
use futures::Future;
use http::Method;
pub use stream::*;
/// Deserializes an HTTP request into the data type.
/// Serializes a data type into an HTTP request, on the client.
///
/// Implementations use the methods of the [`ClientReq`](crate::ClientReq) trait to
/// convert data into a request body. They are often quite short, usually consisting
/// of just two steps:
/// 1. Serializing the data into some [`String`], [`Bytes`](bytes::Bytes), or [`Stream`](futures::Stream).
/// 2. Creating a request with a body of that type.
///
/// For example, heres the implementation for [`Json`].
///
/// ```rust
/// impl<CustErr, T, Request> IntoReq<CustErr, Request, Json> for T
/// where
/// Request: ClientReq<CustErr>,
/// T: Serialize + Send,
/// {
/// fn into_req(
/// self,
/// path: &str,
/// accepts: &str,
/// ) -> Result<Request, ServerFnError<CustErr>> {
/// // try to serialize the data
/// let data = serde_json::to_string(&self)
/// .map_err(|e| ServerFnError::Serialization(e.to_string()))?;
/// // and use it as the body of a POST request
/// Request::try_new_post(path, accepts, Json::CONTENT_TYPE, data)
/// }
/// }
/// ```
pub trait IntoReq<CustErr, Request, Encoding> {
/// Attempts to serialize the arguments into an HTTP request.
fn into_req(
self,
path: &str,
accepts: &str,
) -> Result<Request, ServerFnError<CustErr>>;
}
/// Deserializes an HTTP request into the data type, on the server.
///
/// Implementations use the methods of the [`Req`](crate::Req) trait to access whatever is
/// needed from the request.
/// needed from the request. They are often quite short, usually consisting
/// of just two steps:
/// 1. Extracting the request body into some [`String`], [`Bytes`](bytes::Bytes), or [`Stream`](futures::Stream).
/// 2. Deserializing that data into the data type.
///
/// For example, heres the implementation for [`Json`].
///
@ -80,46 +121,89 @@ pub trait FromReq<CustErr, Request, Encoding>
where
Self: Sized,
{
/// Attempts to deserialize the request.
/// Attempts to deserialize the arguments from a request.
fn from_req(
req: Request,
) -> impl Future<Output = Result<Self, ServerFnError<CustErr>>> + Send;
}
pub trait IntoReq<CustErr, Request, Encoding> {
fn into_req(
self,
path: &str,
accepts: &str,
) -> Result<Request, ServerFnError<CustErr>>;
}
pub trait FromRes<CustErr, Response, Encoding>
where
Self: Sized,
{
fn from_res(
res: Response,
) -> impl Future<Output = Result<Self, ServerFnError<CustErr>>> + Send;
}
/// Serializes the data type into an HTTP response.
///
/// Implementations use the methods of the [`Res`](crate::Res) trait to create a
/// response. They are often quite short, usually consisting
/// of just two steps:
/// 1. Serializing the data type to a [`String`], [`Bytes`](bytes::Bytes), or a [`Stream`](futures::Stream).
/// 2. Creating a response with that serialized value as its body.
///
/// For example, heres the implementation for [`Json`].
///
/// ```rust
/// impl<CustErr, T, Response> IntoRes<CustErr, Response, Json> for T
/// where
/// Response: Res<CustErr>,
/// T: Serialize + Send,
/// {
/// async fn into_res(self) -> Result<Response, ServerFnError<CustErr>> {
/// // try to serialize the data
/// let data = serde_json::to_string(&self)
/// .map_err(|e| ServerFnError::Serialization(e.to_string()))?;
/// // and use it as the body of a response
/// Response::try_from_string(Json::CONTENT_TYPE, data)
/// }
/// }
/// ```
pub trait IntoRes<CustErr, Response, Encoding> {
/// Attempts to serialize the output into an HTTP response.
fn into_res(
self,
) -> impl Future<Output = Result<Response, ServerFnError<CustErr>>> + Send;
}
pub trait Encoding {
const CONTENT_TYPE: &'static str;
const METHOD: Method;
}
pub trait FormDataEncoding<Client, CustErr, Request>
/// Deserializes the data type from an HTTP response.
///
/// Implementations use the methods of the [`ClientRes`](crate::ClientRes) trait to extract
/// data from a response. They are often quite short, usually consisting
/// of just two steps:
/// 1. Extracting a [`String`], [`Bytes`](bytes::Bytes), or a [`Stream`](futures::Stream)
/// from the response body.
/// 2. Deserializing the data type from that value.
///
/// For example, heres the implementation for [`Json`].
///
/// ```rust
/// impl<CustErr, T, Response> FromRes<CustErr, Response, Json> for T
/// where
/// Response: ClientRes<CustErr> + Send,
/// T: DeserializeOwned + Send,
/// {
/// async fn from_res(
/// res: Response,
/// ) -> Result<Self, ServerFnError<CustErr>> {
/// // 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()))
/// }
/// }
/// ```
pub trait FromRes<CustErr, Response, Encoding>
where
Self: Sized,
Client: ClientReq<CustErr>,
{
fn form_data_into_req(
form_data: Client::FormData,
) -> Result<Self, ServerFnError<CustErr>>;
/// Attempts to deserialize the outputs from a response.
fn from_res(
res: Response,
) -> impl Future<Output = Result<Self, ServerFnError<CustErr>>> + Send;
}
/// Defines a particular encoding format, which can be used for serializing or deserializing data.
pub trait Encoding {
/// The MIME type of the data.
const CONTENT_TYPE: &'static str;
/// The HTTP method used for requests.
///
/// This should be `POST` in most cases.
const METHOD: Method;
}

View file

@ -9,6 +9,9 @@ use http::Method;
use multer::Multipart;
use web_sys::FormData;
/// Encodes multipart form data.
///
/// You should primarily use this if you are trying to handle file uploads.
pub struct MultipartFormData;
impl Encoding for MultipartFormData {
@ -16,26 +19,35 @@ impl Encoding for MultipartFormData {
const METHOD: Method = Method::POST;
}
/// Describes whether the multipart data is on the client side or the server side.
#[derive(Debug)]
pub enum MultipartData {
/// `FormData` from the browser.
Client(BrowserFormData),
/// Generic multipart form using [`multer`]. This implements [`Stream`](futures::Stream).
Server(multer::Multipart<'static>),
}
impl MultipartData {
/// Extracts the inner data to handle as a stream.
///
/// On the server side, this always returns `Some(_)`. On the client side, always returns `None`.
pub fn into_inner(self) -> Option<Multipart<'static>> {
match self {
MultipartData::Client(_) => None,
MultipartData::Server(data) => Some(data),
}
}
/// Extracts the inner form data on the client side.
///
/// On the server side, this always returns `None`. On the client side, always returns `Some(_)`.
pub fn into_client_data(self) -> Option<BrowserFormData> {
match self {
MultipartData::Client(data) => Some(data),
MultipartData::Server(_) => None,
}
}
pub fn into_data(self) -> Option<Multipart<'static>> {
match self {
MultipartData::Client(_) => None,
MultipartData::Server(data) => Some(data),
}
}
}
impl From<FormData> for MultipartData {

View file

@ -74,7 +74,7 @@ where
async fn from_res(res: Response) -> Result<Self, ServerFnError<CustErr>> {
let data = res.try_into_string().await?;
Self::deserialize(
&&serde_json::from_str(&data)
&serde_json::from_str(&data)
.map_err(|e| ServerFnError::Args(e.to_string()))?,
)
.map_err(|e| ServerFnError::Deserialization(e.to_string()))

View file

@ -9,6 +9,9 @@ use futures::{Stream, StreamExt};
use http::Method;
use std::pin::Pin;
/// An encoding that represents a stream of bytes.
///
/// A server function that uses this as its output encoding should return [`ByteStream`].
pub struct Streaming;
impl Encoding for Streaming {
@ -36,11 +39,15 @@ where
}
} */
/// A stream of bytes.
///
/// A server function can return this type if its output encoding is [`Streaming`].
pub struct ByteStream<CustErr = NoCustomError>(
Pin<Box<dyn Stream<Item = Result<Bytes, ServerFnError<CustErr>>> + Send>>,
);
impl<CustErr> ByteStream<CustErr> {
/// Consumes the wrapper, returning a stream of bytes.
pub fn into_inner(
self,
) -> impl Stream<Item = Result<Bytes, ServerFnError<CustErr>>> + Send {
@ -79,6 +86,9 @@ where
}
}
/// An encoding that represents a stream of text.
///
/// A server function that uses this as its output encoding should return [`TextStream`].
pub struct StreamingText;
impl Encoding for StreamingText {
@ -86,11 +96,15 @@ impl Encoding for StreamingText {
const METHOD: Method = Method::POST;
}
/// A stream of bytes.
///
/// A server function can return this type if its output encoding is [`StreamingText`].
pub struct TextStream<CustErr = NoCustomError>(
Pin<Box<dyn Stream<Item = Result<String, ServerFnError<CustErr>>> + Send>>,
);
impl<CustErr> TextStream<CustErr> {
/// Consumes the wrapper, returning a stream of text.
pub fn into_inner(
self,
) -> impl Stream<Item = Result<String, ServerFnError<CustErr>>> + Send {

View file

@ -75,6 +75,8 @@ impl FromStr for NoCustomError {
}
}
/// Wraps some error type, which may implement any of [`Error`], [`Clone`], or
/// [`Display`].
#[derive(Debug)]
pub struct WrapError<T>(pub T);
@ -98,6 +100,7 @@ macro_rules! server_fn_error {
/// This trait serves as the conversion method between a variety of types
/// and [`ServerFnError`].
pub trait ViaError<E> {
/// Converts something into an error.
fn to_server_error(&self) -> ServerFnError<E>;
}
@ -160,6 +163,7 @@ impl<E> ViaError<E> for WrapError<E> {
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ServerFnError<E = NoCustomError> {
/// 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).
Registration(String),
@ -180,6 +184,7 @@ pub enum ServerFnError<E = NoCustomError> {
}
impl ServerFnError<NoCustomError> {
/// Constructs a new [`ServerFnError::ServerError`] from some other type.
pub fn new(msg: impl ToString) -> Self {
Self::ServerError(msg.to_string())
}
@ -230,9 +235,20 @@ 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;
}
@ -327,6 +343,7 @@ where
/// it is easy to convert between the two types.
#[derive(Error, Debug, Clone)]
pub enum ServerFnErrorErr<E = NoCustomError> {
/// A user-defined custom error type, which defaults to [`NoCustomError`].
#[error("internal error: {0}")]
WrappedServerError(E),
/// Error while trying to register the server function (only occurs in case of poisoned RwLock).

View file

@ -1,6 +1,6 @@
#![forbid(unsafe_code)]
// uncomment this if you want to feel pain
//#![deny(missing_docs)]
#![deny(missing_docs)]
//! # Server Functions
//!

View file

@ -1,18 +1,25 @@
use std::{future::Future, pin::Pin};
/// An abstraction over a middleware layer, which can be used to add additional
/// middleware layer to a [`Service`].
pub trait Layer<Req, Res>: Send + Sync + 'static {
/// Adds this layer to the inner service.
fn layer(&self, inner: BoxedService<Req, Res>) -> BoxedService<Req, Res>;
}
/// 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>);
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))
}
}
/// A service converts an HTTP request into a response.
pub trait Service<Request, Response> {
/// Converts a request into a response.
fn run(
&mut self,
req: Request,

View file

@ -5,9 +5,15 @@ use futures::Stream;
use send_wrapper::SendWrapper;
use std::future::Future;
/// A wrapped Actix request.
///
/// This uses a [`SendWrapper`] that allows the Actix `HttpRequest` type to be `Send`, but panics
/// if it it is ever sent to another thread. Actix pins request handling to a single thread, so this
/// is necessary to be compatible with traits that require `Send` but should never panic in actual use.
pub struct ActixRequest(pub(crate) SendWrapper<(HttpRequest, Payload)>);
impl ActixRequest {
/// Returns the raw Actix request, and its body.
pub fn take(self) -> (HttpRequest, Payload) {
self.0.take()
}

View file

@ -6,6 +6,7 @@ use js_sys::Uint8Array;
use send_wrapper::SendWrapper;
use web_sys::{FormData, UrlSearchParams};
/// A `fetch` request made in the browser.
#[derive(Debug)]
pub struct BrowserRequest(pub(crate) SendWrapper<Request>);
@ -15,6 +16,7 @@ impl From<Request> for BrowserRequest {
}
}
/// The `FormData` type available in the browser.
#[derive(Debug)]
pub struct BrowserFormData(pub(crate) SendWrapper<FormData>);

View file

@ -3,13 +3,17 @@ use bytes::Bytes;
use futures::Stream;
use std::future::Future;
#[cfg(feature = "actix")]
/// Request types for Actix.
#[cfg(any(feature = "actix", doc))]
pub mod actix;
#[cfg(feature = "axum")]
/// Request types for Axum.
#[cfg(any(feature = "axum", doc))]
pub mod axum;
#[cfg(feature = "browser")]
/// Request types for the browser.
#[cfg(any(feature = "browser", doc))]
pub mod browser;
#[cfg(feature = "reqwest")]
/// Request types for [`reqwest`].
#[cfg(any(feature = "reqwest", doc))]
pub mod reqwest;
/// Represents a request as made by the client.
@ -17,8 +21,10 @@ pub trait ClientReq<CustErr>
where
Self: Sized,
{
/// The type used for URL-encoded form data in this client.
type FormData;
/// Attempts to construct a new `GET` request.
fn try_new_get(
path: &str,
content_type: &str,
@ -26,6 +32,7 @@ where
query: &str,
) -> Result<Self, ServerFnError<CustErr>>;
/// Attempts to construct a new `POST` request with a text body.
fn try_new_post(
path: &str,
content_type: &str,
@ -33,6 +40,7 @@ where
body: String,
) -> Result<Self, ServerFnError<CustErr>>;
/// Attempts to construct a new `POST` request with a binary body.
fn try_new_post_bytes(
path: &str,
content_type: &str,
@ -40,6 +48,7 @@ where
body: Bytes,
) -> Result<Self, ServerFnError<CustErr>>;
/// Attempts to construct a new `POST` request with form data as the body.
fn try_new_post_form_data(
path: &str,
accepts: &str,
@ -47,6 +56,7 @@ where
body: Self::FormData,
) -> Result<Self, ServerFnError<CustErr>>;
/// Attempts to construct a new `POST` request with a multipart body.
fn try_new_multipart(
path: &str,
accepts: &str,

View file

@ -95,12 +95,12 @@ impl<CustErr> ClientReq<CustErr> for Request {
content_type: &str,
body: Self::FormData,
) -> Result<Self, ServerFnError<CustErr>> {
/*CLIENT
.post(path)
.header(ACCEPT, accepts)
.multipart(body)
.build()
.map_err(|e| ServerFnError::Request(e.to_string()))*/
todo!()
CLIENT
.post(path)
.header(CONTENT_TYPE, content_type)
.header(ACCEPT, accepts)
.multipart(body)
.build()
.map_err(|e| ServerFnError::Request(e.to_string()))
}
}

View file

@ -9,9 +9,15 @@ use futures::{Stream, StreamExt};
use send_wrapper::SendWrapper;
use std::fmt::{Debug, Display};
/// A wrapped Actix response.
///
/// This uses a [`SendWrapper`] that allows the Actix `HttpResponse` type to be `Send`, but panics
/// if it it is ever sent to another thread. Actix pins request handling to a single thread, so this
/// is necessary to be compatible with traits that require `Send` but should never panic in actual use.
pub struct ActixResponse(pub(crate) SendWrapper<HttpResponse>);
impl ActixResponse {
/// Returns the raw Actix response.
pub fn take(self) -> HttpResponse {
self.0.take()
}

View file

@ -9,6 +9,7 @@ use std::future::Future;
use wasm_bindgen::JsCast;
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 {

View file

@ -1,10 +1,14 @@
#[cfg(feature = "actix")]
/// Response types for Actix.
#[cfg(any(feature = "actix", doc))]
pub mod actix;
#[cfg(feature = "browser")]
/// Response types for the browser.
#[cfg(any(feature = "browser", doc))]
pub mod browser;
#[cfg(feature = "axum")]
/// Response types for Axum.
#[cfg(any(feature = "axum", doc))]
pub mod http;
#[cfg(feature = "reqwest")]
/// Response types for [`reqwest`].
#[cfg(any(feature = "reqwest", doc))]
pub mod reqwest;
use crate::error::ServerFnError;
@ -37,6 +41,7 @@ where
+ 'static,
) -> Result<Self, ServerFnError<CustErr>>;
/// Converts an error into a response, with a `500` status code and the error text as its body.
fn error_response(err: ServerFnError<CustErr>) -> Self;
}