#![feature(trait_alias, const_generics, type_alias_impl_trait)]
#![allow(incomplete_features, clippy::type_complexity)]
#![doc(test(
no_crate_inject,
attr(deny(rust_2018_idioms, unused_imports, unused_mut))
))]
#![warn(rust_2018_idioms, missing_docs)]
pub use hyperbole_macros::record_args;
#[doc(hidden)]
pub use frunk_core;
pub mod body;
mod combinators;
pub mod field;
mod handler;
pub mod mw;
pub mod prelude;
pub mod reply;
pub mod test;
pub mod tree;
use combinators::{
Add2, Base, End, Inject, InjectAll, Link, Map, MapErr, MapErrs, Path, Then, TryMap, TryThen,
};
use handler::{Chain, Handler, NotFound};
use reply::{redirect, Reply};
use tree::{Cluster, Node, Params, Parser, PathSpec, Route, Segment};
use frunk_core::{
hlist::{HList, HNil},
indices::Here,
};
use futures::future::{ready, BoxFuture, FutureExt, NeverError, Ready};
use http::{Extensions, HeaderMap, Uri, Version};
use hyper::{server::conn::AddrStream, service::Service, Body, Method, Request};
use std::{
borrow::Cow,
collections::HashMap,
convert::Infallible,
future::Future,
net::SocketAddr,
ops::{Add, Deref},
sync::Arc,
task::{Context, Poll},
};
pub type Response = hyper::Response<Body>;
#[doc(hidden)]
#[derive(Clone)]
pub struct AppDispatch(Arc<App>);
impl Service<&AddrStream> for AppDispatch {
type Response = AppService;
type Error = Infallible;
type Future = NeverError<Ready<Self::Response>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, tgt: &AddrStream) -> Self::Future {
let svc = AppService {
app: Arc::clone(&self.0),
addr: tgt.remote_addr(),
};
ready(svc).never_error()
}
}
#[doc(hidden)]
#[derive(Clone)]
pub struct AppService {
app: Arc<App>,
addr: SocketAddr,
}
impl Deref for AppService {
type Target = App;
fn deref(&self) -> &Self::Target {
&self.app
}
}
impl Service<Request<Body>> for AppService {
type Response = Response;
type Error = Infallible;
type Future = NeverError<BoxFuture<'static, Response>>;
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request<Body>) -> Self::Future {
self.dispatch(req, self.addr).never_error()
}
}
pub type Init = R![Body, Method, Uri, Version, HeaderMap, Extensions, SocketAddr];
pub struct App {
common: [Node<dyn Handler>; 9],
custom: HashMap<Method, Node<dyn Handler>>,
not_found: Box<dyn Handler>,
}
impl Default for App {
fn default() -> Self {
Self::new()
}
}
impl App {
pub fn new() -> Self {
Self {
common: Default::default(),
custom: HashMap::new(),
not_found: Box::new(NotFound),
}
}
pub fn context_path<P: HList + Parser<Segment> + Send>(
self,
spec: PathSpec<P>,
) -> Ctx<Params<P>, Path<Base, P, Here>, App>
{
Ctx::with_source(self).path(spec)
}
pub fn context(self) -> Ctx<HNil, Base, App> {
Ctx::with_source(self)
}
pub fn not_found<F, Args, Ix, Fut, Resp>(mut self, handler: F) -> Self
where
F: Fn(Args) -> Fut + Sync + Send + 'static,
Fut: Future<Output = Resp> + Send + 'static,
Resp: Reply + 'static,
End<Base, F, Args, Ix>: Link<Init, HNil, Output = Response, Params = HNil> + 'static,
{
let chain: Chain<HNil, End<Base, F, Args, Ix>> =
Chain::new(uri![]).link_next(|link| End::new(link, handler));
self.not_found = Box::new(chain);
self
}
pub fn into_make_service(self) -> AppDispatch {
AppDispatch(Arc::new(self))
}
fn get_node_mut(&mut self, method: Method) -> &mut Node<dyn Handler> {
if let Some(i) = method_idx(&method) {
&mut self.common[i]
} else {
self.custom.entry(method).or_default()
}
}
fn lookup_route(&self, method: &Method, path: &str) -> Route<'_, dyn Handler> {
method_idx(method)
.map(|i| &self.common[i])
.or_else(|| self.custom.get(method))
.map(|n| n.lookup(path))
.unwrap_or_default()
}
fn dispatch(&self, req: Request<Body>, addr: SocketAddr) -> BoxFuture<'static, Response> {
#[inline]
fn swap_trailing_slash(path: &str) -> Cow<'_, str> {
if let Some(stripped) = path.strip_suffix('/') {
return Cow::Borrowed(stripped);
}
let mut s = String::with_capacity(path.len() + 1);
s.push_str(path);
s.push('/');
Cow::Owned(s)
}
let path = req.uri().path();
let route = self.lookup_route(req.method(), path);
if let Some(h) = route.entry {
return h.handle(req, addr);
}
if route.tsr && req.method() != Method::CONNECT && path != "/" {
return ready(redirect(false, swap_trailing_slash(path))).boxed();
}
self.not_found.handle(req, addr)
}
pub fn test_client(self) -> test::Client {
test::Client { app: self }
}
pub fn merge(mut self, routes: Routes) -> Self {
for r in routes.inner {
self.get_node_mut(r.method).insert(&r.path, r.handler);
}
self
}
}
#[inline]
fn method_idx(m: &Method) -> Option<usize> {
Some(match *m {
Method::GET => 0,
Method::POST => 1,
Method::PUT => 2,
Method::DELETE => 3,
Method::HEAD => 4,
Method::OPTIONS => 5,
Method::CONNECT => 6,
Method::PATCH => 7,
Method::TRACE => 8,
_ => return None,
})
}
pub struct Ctx<P, L, S = ()> {
source: S,
routes: Vec<RouteEntry>,
chain: Chain<P, L>,
}
macro_rules! handle {
($( [$name:ident, $method:path] ),+) => {
$(handle! {@withdoc
concat!(
"A convenience method to call [handle][Ctx::handle] with ",
stringify!([$method].),
),
$name,
$method,
})+
};
(@withdoc $desc:expr, $name:ident, $method:path $(,)?) => {
#[doc = $desc]
pub fn $name<_P, F, Args, Ix, Pix, Fut, Resp>(self, spec: PathSpec<_P>, handler: F) -> Self
where
F: Fn(Args) -> Fut + Sync + Send,
Fut: Future<Output = Resp> + Send,
Resp: Reply ,
(): CtxState2<L, P, _P, Pix, F, Args, Ix>,
{
self.handle($method, spec, handler)
}
};
}
macro_rules! handle_with {
($( [$name:ident, $method:path] ),+) => {
$(handle_with! {@withdoc
concat!(
"A convenience method to call [handle_with][Ctx::handle_with] with ",
stringify!([$method].),
),
$name,
$method,
})+
};
(@withdoc $desc:expr, $name:ident, $method:path $(,)?) => {
#[doc = $desc]
pub fn $name<_P, Pix, W, WArgs, WFut, Merge, E, Wix, F, Args, Fut, Resp, Ix>(
self,
spec: PathSpec<_P>,
with: W,
handler: F,
) -> Self
where
W: Fn(WArgs) -> WFut + Sync + Send,
WFut: Future<Output = Result<Merge, E>> + Send,
E: Reply,
F: Fn(Args) -> Fut + Sync + Send,
Fut: Future<Output = Resp> + Send,
Resp: Reply,
(): CtxState3<L, P, _P, Pix, W, WArgs, Wix, F, Args, Ix>,
{
self.handle_with($method, spec, with, handler)
}
};
}
impl Default for Ctx<HNil, Base> {
fn default() -> Self {
Self::with_source(())
}
}
impl<S> Ctx<HNil, Base, S> {
fn with_source(source: S) -> Self {
Self {
source,
routes: vec![],
chain: Chain::new(uri![]),
}
}
}
impl<P: HList + Send + Parser<Segment>> Ctx<Params<P>, Path<Base, P, Here>> {
pub fn with_path(spec: PathSpec<P>) -> Self {
Ctx::default().path(spec)
}
}
impl<T: Clone> Ctx<HNil, InjectAll<Base, T>>
where InjectAll<Base, T>: Link<Init, HNil>
{
pub fn with_state(values: T) -> Self {
Ctx::default().inject_all(values)
}
}
impl<P: 'static, L: Sync + Send + Clone + 'static, S> Ctx<P, L, S> {
fn link_next<Ln, F: FnOnce(L) -> Ln>(self, wrap: F) -> Ctx<P, Ln, S> {
Ctx {
source: self.source,
routes: self.routes,
chain: self.chain.link_next(wrap),
}
}
pub fn inject<T: Clone>(self, value: T) -> Ctx<P, Inject<L, T>, S>
where Inject<L, T>: Link<Init, P> {
self.link_next(|link| Inject::new(link, value))
}
pub fn inject_all<T: Clone>(self, values: T) -> Ctx<P, InjectAll<L, T>, S>
where InjectAll<L, T>: Link<Init, P> {
self.link_next(|link| InjectAll::new(link, values))
}
pub fn map<F, Args, Ix, Merge>(self, f: F) -> Ctx<P, Map<L, F, Args, Ix>, S>
where
F: Fn(Args) -> Merge,
Merge: HList,
Map<L, F, Args, Ix>: Link<Init, P>,
{
self.link_next(|link| Map::new(link, f))
}
pub fn try_map<F, Args, Ix, Merge, E>(self, f: F) -> Ctx<P, TryMap<L, F, Args, Ix>, S>
where
F: Fn(Args) -> Result<Merge, E>,
Merge: HList,
E: Reply,
TryMap<L, F, Args, Ix>: Link<Init, P>,
{
self.link_next(|link| TryMap::new(link, f))
}
pub fn then<F, Args, Ix, Fut, Merge>(self, f: F) -> Ctx<P, Then<L, F, Args, Ix>, S>
where
F: Fn(Args) -> Fut,
Fut: Future<Output = Merge>,
Merge: HList,
Then<L, F, Args, Ix>: Link<Init, P>,
{
self.link_next(|link| Then::new(link, f))
}
pub fn try_then<F, Args, Ix, Fut, Merge, E>(self, f: F) -> Ctx<P, TryThen<L, F, Args, Ix>, S>
where
F: Fn(Args) -> Fut,
Fut: Future<Output = Result<Merge, E>>,
Merge: HList,
E: Reply,
TryThen<L, F, Args, Ix>: Link<Init, P>,
{
self.link_next(|link| TryThen::new(link, f))
}
pub fn map_errs<F, E>(self, f: F) -> Ctx<P, MapErrs<L, F>, S>
where
F: Fn(<L as Link<Init, P>>::Error) -> E,
E: Reply,
L: Link<Init, P>,
MapErrs<L, F>: Link<Init, P>,
{
self.link_next(|link| MapErrs::new(link, f))
}
pub fn map_err<F, E, Ix, R>(self, f: F) -> Ctx<P, MapErr<L, F, E, Ix>, S>
where
F: Fn(E) -> R,
R: Reply,
MapErr<L, F, E, Ix>: Link<Init, P>,
{
self.link_next(|link| MapErr::new(link, f))
}
pub fn path<_P, Ix>(self, spec: PathSpec<_P>) -> Ctx<Add2<P, Params<_P>>, Path<L, _P, Ix>, S>
where
P: Add<Params<_P>>,
_P: Parser<Segment>,
Path<L, _P, Ix>: Link<Init, Add2<P, Params<_P>>>,
{
Ctx {
source: self.source,
routes: self.routes,
chain: self.chain.add_path(spec).link_next(Path::new),
}
}
pub fn handle<_P, F, Args, Ix, Pix, Fut, Resp>(
mut self,
method: Method,
spec: PathSpec<_P>,
handler: F,
) -> Self
where
F: Fn(Args) -> Fut + Sync + Send,
Fut: Future<Output = Resp> + Send,
Resp: Reply,
(): CtxState2<L, P, _P, Pix, F, Args, Ix>,
{
let chain = (self.chain.clone())
.add_path(spec)
.link_next(|link| -> Path<_, _P, Pix> { Path::new(link) })
.link_next(|link| -> End<_, F, Args, Ix> { End::new(link, handler) });
self.routes.push(RouteEntry {
method,
path: format!("{}", chain.path()),
handler: Box::new(chain),
});
self
}
handle!(
[get, Method::GET],
[post, Method::POST],
[put, Method::PUT],
[patch, Method::PATCH],
[delete, Method::DELETE]
);
pub fn handle_with<_P, Pix, W, WArgs, WFut, Merge, E, Wix, F, Args, Fut, Resp, Ix>(
mut self,
method: Method,
spec: PathSpec<_P>,
with: W,
handler: F,
) -> Self
where
W: Fn(WArgs) -> WFut + Sync + Send,
WFut: Future<Output = Result<Merge, E>> + Send,
E: Reply,
F: Fn(Args) -> Fut + Sync + Send,
Fut: Future<Output = Resp> + Send,
Resp: Reply,
(): CtxState3<L, P, _P, Pix, W, WArgs, Wix, F, Args, Ix>,
{
let chain = (self.chain.clone())
.add_path(spec)
.link_next(|link| -> Path<_, _P, Pix> { Path::new(link) })
.link_next(|link| -> TryThen<_, W, WArgs, Wix> { TryThen::new(link, with) })
.link_next(|link| -> End<_, F, Args, Ix> { End::new(link, handler) });
self.routes.push(RouteEntry {
method,
path: format!("{}", chain.path()),
handler: Box::new(chain),
});
self
}
handle_with!(
[get_with, Method::GET],
[post_with, Method::POST],
[put_with, Method::PUT],
[patch_with, Method::PATCH],
[delete_with, Method::DELETE]
);
pub fn into_routes(self) -> Routes {
Routes { inner: self.routes }
}
}
impl<P: 'static, L: Sync + Send + Clone + 'static> Ctx<P, L, App> {
pub fn collapse(self) -> App {
self.source.merge(Routes { inner: self.routes })
}
}
#[doc(hidden)]
pub trait CtxState2<L, P, _P, Pix, F, Args, Ix> = where
P: Add<Params<_P>>,
_P: Parser<Segment>,
Add2<P, Params<_P>>: Parser<Cluster>,
End<Path<L, _P, Pix>, F, Args, Ix>:
Link<Init, Add2<P, Params<_P>>, Output = Response, Params = HNil> + 'static;
#[doc(hidden)]
pub trait CtxState3<L, P, _P, Pix, W, WArgs, Wix, F, Args, Ix> = where
P: Add<Params<_P>>,
_P: Parser<Segment>,
Add2<P, Params<_P>>: Parser<Cluster>,
End<TryThen<Path<L, _P, Pix>, W, WArgs, Wix>, F, Args, Ix>:
Link<Init, Add2<P, Params<_P>>, Output = Response, Params = HNil> + 'static;
pub struct Routes {
inner: Vec<RouteEntry>,
}
struct RouteEntry {
method: Method,
path: String,
handler: Box<dyn Handler>,
}