#![forbid(unsafe_code)]
#![deny(missing_docs)]
pub mod client;
pub mod codec;
#[macro_use]
pub mod error;
pub mod middleware;
pub mod redirect;
pub mod request;
pub mod response;
#[cfg(feature = "actix")]
#[doc(hidden)]
pub use ::actix_web as actix_export;
#[cfg(feature = "axum-no-default")]
#[doc(hidden)]
pub use ::axum as axum_export;
use client::Client;
use codec::{Encoding, FromReq, FromRes, IntoReq, IntoRes};
#[doc(hidden)]
pub use const_format;
use dashmap::DashMap;
pub use error::ServerFnError;
use error::ServerFnErrorSerde;
#[cfg(feature = "form-redirects")]
use error::ServerFnUrlError;
use http::Method;
use middleware::{Layer, Service};
use once_cell::sync::Lazy;
use redirect::RedirectHook;
use request::Req;
use response::{ClientRes, Res};
#[cfg(feature = "rkyv")]
pub use rkyv;
#[doc(hidden)]
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};
#[doc(hidden)]
pub use xxhash_rust;
pub trait ServerFn
where
Self: Send
+ FromReq<Self::InputEncoding, Self::ServerRequest, Self::Error>
+ IntoReq<
Self::InputEncoding,
<Self::Client as Client<Self::Error>>::Request,
Self::Error,
>,
{
const PATH: &'static str;
type Client: Client<Self::Error>;
type ServerRequest: Req<Self::Error> + Send;
type ServerResponse: Res<Self::Error> + Send;
type Output: IntoRes<Self::OutputEncoding, Self::ServerResponse, Self::Error>
+ FromRes<
Self::OutputEncoding,
<Self::Client as Client<Self::Error>>::Response,
Self::Error,
> + Send;
type InputEncoding: Encoding;
type OutputEncoding: Encoding;
type Error: FromStr + Display;
fn url() -> &'static str {
Self::PATH
}
fn middlewares(
) -> Vec<Arc<dyn Layer<Self::ServerRequest, Self::ServerResponse>>> {
Vec::new()
}
fn run_body(
self,
) -> impl Future<Output = Result<Self::Output, ServerFnError<Self::Error>>> + Send;
#[doc(hidden)]
fn run_on_server(
req: Self::ServerRequest,
) -> impl Future<Output = Self::ServerResponse> + Send {
#[cfg(feature = "form-redirects")]
let accepts_html = req
.accepts()
.map(|n| n.contains("text/html"))
.unwrap_or(false);
#[cfg(feature = "form-redirects")]
let mut referer = req.referer().as_deref().map(ToOwned::to_owned);
async move {
#[allow(unused_variables, unused_mut)]
let (mut res, err) = Self::execute_on_server(req)
.await
.map(|res| (res, None))
.unwrap_or_else(|e| {
(
Self::ServerResponse::error_response(Self::PATH, &e),
Some(e),
)
});
#[cfg(feature = "form-redirects")]
if accepts_html {
if let Some(err) = err {
if let Ok(url) = ServerFnUrlError::new(Self::PATH, err)
.to_url(referer.as_deref().unwrap_or("/"))
{
referer = Some(url.to_string());
}
}
else if let Some(referer) = referer.as_mut() {
ServerFnUrlError::<Self::Error>::strip_error_info(referer)
}
res.redirect(referer.as_deref().unwrap_or("/"));
}
res
}
}
#[doc(hidden)]
fn run_on_client(
self,
) -> impl Future<Output = Result<Self::Output, ServerFnError<Self::Error>>> + Send
{
async move {
let req =
self.into_req(Self::PATH, Self::OutputEncoding::CONTENT_TYPE)?;
Self::run_on_client_with_req(req, redirect::REDIRECT_HOOK.get())
.await
}
}
#[doc(hidden)]
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
{
async move {
let res = Self::Client::send(req).await?;
let status = res.status();
let location = res.location();
let has_redirect_header = res.has_redirect();
let res = if (400..=599).contains(&status) {
let text = res.try_into_string().await?;
Err(ServerFnError::<Self::Error>::de(&text))
} else {
Ok(Self::Output::from_res(res).await)
}?;
if let Some(redirect_hook) = redirect_hook {
if (300..=399).contains(&status) || has_redirect_header {
redirect_hook(&location);
}
}
res
}
}
#[doc(hidden)]
fn execute_on_server(
req: Self::ServerRequest,
) -> impl Future<
Output = Result<Self::ServerResponse, ServerFnError<Self::Error>>,
> + Send {
async {
let this = Self::from_req(req).await?;
let output = this.run_body().await?;
let res = output.into_res().await?;
Ok(res)
}
}
}
#[cfg(feature = "ssr")]
#[doc(hidden)]
pub use inventory;
#[macro_export]
macro_rules! initialize_server_fn_map {
($req:ty, $res:ty) => {
once_cell::sync::Lazy::new(|| {
$crate::inventory::iter::<ServerFnTraitObj<$req, $res>>
.into_iter()
.map(|obj| {
((obj.path().to_string(), obj.method()), obj.clone())
})
.collect()
})
};
}
pub type MiddlewareSet<Req, Res> = Vec<Arc<dyn Layer<Req, Res>>>;
pub struct ServerFnTraitObj<Req, Res> {
path: &'static str,
method: Method,
handler: fn(Req) -> Pin<Box<dyn Future<Output = Res> + Send>>,
middleware: fn() -> MiddlewareSet<Req, Res>,
}
impl<Req, Res> ServerFnTraitObj<Req, Res> {
pub const fn new(
path: &'static str,
method: Method,
handler: fn(Req) -> Pin<Box<dyn Future<Output = Res> + Send>>,
middleware: fn() -> MiddlewareSet<Req, Res>,
) -> Self {
Self {
path,
method,
handler,
middleware,
}
}
pub fn path(&self) -> &'static str {
self.path
}
pub fn method(&self) -> Method {
self.method.clone()
}
pub fn handler(&self, req: Req) -> impl Future<Output = Res> + Send {
(self.handler)(req)
}
pub fn middleware(&self) -> MiddlewareSet<Req, Res> {
(self.middleware)()
}
}
impl<Req, Res> Service<Req, Res> for ServerFnTraitObj<Req, Res>
where
Req: Send + 'static,
Res: 'static,
{
fn run(&mut self, req: Req) -> Pin<Box<dyn Future<Output = Res> + Send>> {
let handler = self.handler;
Box::pin(async move { handler(req).await })
}
}
impl<Req, Res> Clone for ServerFnTraitObj<Req, Res> {
fn clone(&self) -> Self {
Self {
path: self.path,
method: self.method.clone(),
handler: self.handler,
middleware: self.middleware,
}
}
}
#[allow(unused)] type LazyServerFnMap<Req, Res> =
Lazy<DashMap<(String, Method), ServerFnTraitObj<Req, Res>>>;
#[cfg(feature = "ssr")]
impl<Req: 'static, Res: 'static> inventory::Collect
for ServerFnTraitObj<Req, Res>
{
#[inline]
fn registry() -> &'static inventory::Registry {
static REGISTRY: inventory::Registry = inventory::Registry::new();
®ISTRY
}
}
#[cfg(feature = "axum-no-default")]
pub mod axum {
use crate::{
middleware::{BoxedService, Service},
Encoding, LazyServerFnMap, ServerFn, ServerFnTraitObj,
};
use axum::body::Body;
use http::{Method, Request, Response, StatusCode};
static REGISTERED_SERVER_FUNCTIONS: LazyServerFnMap<
Request<Body>,
Response<Body>,
> = initialize_server_fn_map!(Request<Body>, Response<Body>);
pub fn register_explicit<T>()
where
T: ServerFn<
ServerRequest = Request<Body>,
ServerResponse = Response<Body>,
> + 'static,
{
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,
),
);
}
pub fn server_fn_paths() -> impl Iterator<Item = (&'static str, Method)> {
REGISTERED_SERVER_FUNCTIONS
.iter()
.map(|item| (item.path(), item.method()))
}
pub async fn handle_server_fn(req: Request<Body>) -> Response<Body> {
let path = req.uri().path();
if let Some(mut service) =
get_server_fn_service(path, req.method().clone())
{
service.run(req).await
} else {
Response::builder()
.status(StatusCode::BAD_REQUEST)
.body(Body::from(format!(
"Could not find a server function at the route {path}. \
\n\nIt's likely that either\n 1. The API prefix you \
specify in the `#[server]` macro doesn't match the \
prefix at which your server function handler is mounted, \
or \n2. You are on a platform that doesn't support \
automatic server function registration and you need to \
call ServerFn::register_explicit() on the server \
function type, somewhere in your `main` function.",
)))
.unwrap()
}
}
pub fn get_server_fn_service(
path: &str,
method: Method,
) -> Option<BoxedService<Request<Body>, Response<Body>>> {
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());
for middleware in middleware {
service = middleware.layer(service);
}
service
})
}
}
#[cfg(feature = "actix")]
pub mod actix {
use crate::{
middleware::BoxedService, request::actix::ActixRequest,
response::actix::ActixResponse, Encoding, LazyServerFnMap, ServerFn,
ServerFnTraitObj,
};
use actix_web::{web::Payload, HttpRequest, HttpResponse};
use http::Method;
#[doc(hidden)]
pub use send_wrapper::SendWrapper;
static REGISTERED_SERVER_FUNCTIONS: LazyServerFnMap<
ActixRequest,
ActixResponse,
> = initialize_server_fn_map!(ActixRequest, ActixResponse);
pub fn register_explicit<T>()
where
T: ServerFn<
ServerRequest = ActixRequest,
ServerResponse = ActixResponse,
> + 'static,
{
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,
),
);
}
pub fn server_fn_paths() -> impl Iterator<Item = (&'static str, Method)> {
REGISTERED_SERVER_FUNCTIONS
.iter()
.map(|item| (item.path(), item.method()))
}
pub async fn handle_server_fn(
req: HttpRequest,
payload: Payload,
) -> HttpResponse {
let path = req.uri().path();
let method = req.method();
if let Some(mut service) = get_server_fn_service(path, method) {
service
.0
.run(ActixRequest::from((req, payload)))
.await
.0
.take()
} else {
HttpResponse::BadRequest().body(format!(
"Could not find a server function at the route {path}. \
\n\nIt's likely that either\n 1. The API prefix you specify \
in the `#[server]` macro doesn't match the prefix at which \
your server function handler is mounted, or \n2. You are on \
a platform that doesn't support automatic server function \
registration and you need to call \
ServerFn::register_explicit() on the server function type, \
somewhere in your `main` function.",
))
}
}
pub fn get_server_fn_service(
path: &str,
method: &actix_web::http::Method,
) -> Option<BoxedService<ActixRequest, ActixResponse>> {
use actix_web::http::Method as ActixMethod;
let method = match *method {
ActixMethod::GET => Method::GET,
ActixMethod::POST => Method::POST,
ActixMethod::PUT => Method::PUT,
ActixMethod::PATCH => Method::PATCH,
ActixMethod::DELETE => Method::DELETE,
ActixMethod::HEAD => Method::HEAD,
ActixMethod::TRACE => Method::TRACE,
ActixMethod::OPTIONS => Method::OPTIONS,
ActixMethod::CONNECT => Method::CONNECT,
_ => unreachable!(),
};
REGISTERED_SERVER_FUNCTIONS.get(&(path.into(), method)).map(
|server_fn| {
let middleware = (server_fn.middleware)();
let mut service = BoxedService::new(server_fn.clone());
for middleware in middleware {
service = middleware.layer(service);
}
service
},
)
}
}