Skip to content

Commit

Permalink
Setup axum-like traits
Browse files Browse the repository at this point in the history
  • Loading branch information
CathalMullan committed Sep 2, 2024
1 parent 48c1fec commit 634e10b
Show file tree
Hide file tree
Showing 16 changed files with 480 additions and 354 deletions.
363 changes: 151 additions & 212 deletions Cargo.lock

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions examples/hyper/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ http = "1.0"
http-body-util = "0.1"
hyper = { version = "1.1", features = ["full"] }
hyper-util = { version = "0.1", features = ["full"] }
reqwest = { version = "0.12", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1.35", features = ["full", "test-util"] }
tokio = { version = "1.35", features = ["full"] }

[dev-dependencies]
reqwest = { version = "0.12", default-features = false, features = [
"rustls-tls",
"json",
] }
2 changes: 0 additions & 2 deletions examples/hyper/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# `hyper` example

WIP

This is a mini implementation of an [Open Container Initiative (OCI) Distribution Specification](https://github.com/opencontainers/distribution-spec/blob/main/spec.md) registry.

Inspirations:
Expand Down
59 changes: 59 additions & 0 deletions examples/hyper/src/extract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use crate::{response::IntoResponse, SharedAppState};
use http::{request::Parts, Request};
use hyper::body::Incoming;
use std::convert::Infallible;

pub mod path;

pub type AppRequest = Request<Incoming>;

mod private {
#[derive(Debug, Clone, Copy)]
pub enum ViaParts {}

#[derive(Debug, Clone, Copy)]
pub enum ViaRequest {}
}

pub trait FromRequestParts: Sized {
type Rejection: IntoResponse;

fn from_request_parts(
parts: &mut Parts,
state: &SharedAppState,
) -> Result<Self, Self::Rejection>;
}

impl FromRequestParts for SharedAppState {
type Rejection = Infallible;

fn from_request_parts(_: &mut Parts, state: &SharedAppState) -> Result<Self, Self::Rejection> {
Ok(state.clone())
}
}

pub trait FromRequest<M = private::ViaRequest>: Sized {
type Rejection: IntoResponse;

fn from_request(req: AppRequest, state: &SharedAppState) -> Result<Self, Self::Rejection>;
}

impl<T> FromRequest<private::ViaParts> for T
where
T: FromRequestParts,
{
type Rejection = <Self as FromRequestParts>::Rejection;

fn from_request(req: AppRequest, state: &SharedAppState) -> Result<Self, Self::Rejection> {
let (mut parts, _) = req.into_parts();
Self::from_request_parts(&mut parts, state)
}
}

impl FromRequest<()> for AppRequest {
type Rejection = Infallible;

fn from_request(req: AppRequest, _: &SharedAppState) -> Result<Self, Self::Rejection> {
Ok(req)
}
}
14 changes: 14 additions & 0 deletions examples/hyper/src/extract/path.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use super::FromRequestParts;
use crate::SharedAppState;
use http::request::Parts;
use std::convert::Infallible;

pub struct Path(pub String);

impl FromRequestParts for Path {
type Rejection = Infallible;

fn from_request_parts(parts: &mut Parts, _: &SharedAppState) -> Result<Self, Self::Rejection> {
Ok(Self(parts.uri.path().to_string()))
}
}
94 changes: 94 additions & 0 deletions examples/hyper/src/handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use crate::{
extract::{AppRequest, FromRequest, FromRequestParts},
response::{AppResponse, IntoResponse},
SharedAppState,
};
use std::{future::Future, pin::Pin};

pub trait Handler<T>: Clone + Send + Sized + 'static {
type Future: Future<Output = AppResponse> + Send + 'static;

fn call(self, req: AppRequest, state: SharedAppState) -> Self::Future;
}

impl<F, Fut, Res> Handler<()> for F
where
F: FnOnce() -> Fut + Clone + Send + 'static,
Fut: Future<Output = Res> + Send,
Res: IntoResponse,
{
type Future = Pin<Box<dyn Future<Output = AppResponse> + Send>>;

fn call(self, _: AppRequest, _: SharedAppState) -> Self::Future {
Box::pin(async move { self().await.into_response() })
}
}

macro_rules! impl_handler {
(
[$($ty:ident),*], $last:ident
) => {
#[allow(non_snake_case, unused_mut)]
impl<F, Fut, Res, M, $($ty,)* $last> Handler<(M, $($ty,)* $last,)> for F
where
F: FnOnce($($ty,)* $last,) -> Fut + Clone + Send + 'static,
Fut: Future<Output = Res> + Send,
Res: IntoResponse,
$( $ty: FromRequestParts + Send, )*
$( <$ty as FromRequestParts>::Rejection: IntoResponse, )*
$last: FromRequest<M> + Send,
<$last as FromRequest<M>>::Rejection: IntoResponse,
{
type Future = Pin<Box<dyn Future<Output = AppResponse> + Send>>;

fn call(self, req: AppRequest, state: SharedAppState) -> Self::Future {
Box::pin(async move {
let (mut parts, body) = req.into_parts();
let state = &state;

$(
let $ty = match $ty::from_request_parts(&mut parts, state) {
Ok(value) => value,
Err(rejection) => return rejection.into_response(),
};
)*

let req = AppRequest::from_parts(parts, body);

let $last = match $last::from_request(req, state) {
Ok(value) => value,
Err(rejection) => return rejection.into_response(),
};

let res = self($($ty,)* $last,).await;

res.into_response()
})
}
}
};
}

#[rustfmt::skip]
macro_rules! all_the_tuples {
($name:ident) => {
$name!([], T1);
$name!([T1], T2);
$name!([T1, T2], T3);
$name!([T1, T2, T3], T4);
$name!([T1, T2, T3, T4], T5);
$name!([T1, T2, T3, T4, T5], T6);
$name!([T1, T2, T3, T4, T5, T6], T7);
$name!([T1, T2, T3, T4, T5, T6, T7], T8);
$name!([T1, T2, T3, T4, T5, T6, T7, T8], T9);
$name!([T1, T2, T3, T4, T5, T6, T7, T8, T9], T10);
$name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T11);
$name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], T12);
$name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], T13);
$name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], T14);
$name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], T15);
$name!([T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], T16);
};
}

all_the_tuples!(impl_handler);
122 changes: 21 additions & 101 deletions examples/hyper/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,110 +1,38 @@
#![allow(clippy::missing_panics_doc, clippy::missing_errors_doc)]

use bytes::Bytes;
use http::{Method, Request, Response, StatusCode};
use http_body_util::Full;
use hyper::body::Incoming;
use http::Method;
use hyper::service::service_fn;
use hyper_util::{
rt::{TokioExecutor, TokioIo},
server::conn::auto::Builder,
};
use std::collections::{BTreeMap, HashMap};
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::{convert::Infallible, error::Error};
use tokio::{net::TcpListener, sync::RwLock, task::JoinSet};

use router::AppRouter;
use std::{collections::BTreeMap, convert::Infallible, error::Error, sync::Arc};
use tokio::{sync::RwLock, task::JoinSet};

pub mod extract;
pub mod handler;
pub mod response;
pub mod router;
pub mod routes;

pub type SharedState = Arc<RwLock<AppState>>;
pub type SharedAppState = Arc<RwLock<AppState>>;

pub struct AppState {
pub db: BTreeMap<String, Bytes>,
}

pub type AppResponse = Response<Full<Bytes>>;

pub type HandlerFn = Arc<
dyn Fn(Request<Incoming>) -> Pin<Box<dyn Future<Output = AppResponse> + Send>> + Send + Sync,
>;

pub struct Router {
routes: HashMap<Method, wayfind::Router<HandlerFn>>,
}

impl Router {
#[must_use]
pub fn new() -> Self {
Self {
routes: HashMap::new(),
}
}

pub fn route<F, Fut>(&mut self, method: Method, path: &str, handler: F) -> &mut Self
where
F: Fn(Request<Incoming>) -> Fut + Send + Sync + 'static,
Fut: Future<Output = AppResponse> + Send + 'static,
{
let handler = Arc::new(move |req| {
Box::pin(handler(req)) as Pin<Box<dyn Future<Output = AppResponse> + Send>>
});

self.routes
.entry(method)
.or_default()
.insert(path, handler)
.unwrap();

self
}

pub async fn handle(&self, req: Request<Incoming>) -> AppResponse {
let method = req.method();
let path = req.uri().path();

let Ok(path) = wayfind::Path::new(path) else {
return Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Full::new(Bytes::from("Not Found")))
.unwrap();
};

if let Some(router) = self.routes.get(method) {
match router.search(&path) {
Ok(Some(search)) => {
let handler = &search.data.value;
handler(req).await
}
_ => Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Full::new(Bytes::from("Not Found")))
.unwrap(),
}
} else {
Response::builder()
.status(StatusCode::METHOD_NOT_ALLOWED)
.body(Full::new(Bytes::from("Method Not Allowed")))
.unwrap()
}
}
}

impl Default for Router {
fn default() -> Self {
Self::new()
}
}

pub async fn start_server(
listener: TcpListener,
listener: tokio::net::TcpListener,
) -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
println!("listening on http://{}", listener.local_addr()?);
let shared_state = Arc::new(RwLock::new(AppState {

let state = Arc::new(RwLock::new(AppState {
db: BTreeMap::new(),
}));

let mut router = Router::new();
let mut router = AppRouter::new();
router
.route(Method::GET, "/{key}", routes::get::handle_get)
.route(Method::POST, "/{key}", routes::set::handle_set)
Expand All @@ -124,14 +52,16 @@ pub async fn start_server(
}
};

let router_clone = router.clone();
let state_clone = shared_state.clone();
let router_clone = Arc::clone(&router);
let state_clone = Arc::clone(&state);

let serve_connection = async move {
println!("handling a request from {peer_addr}");

let service = hyper::service::service_fn(move |req| {
handle_request(req, router_clone.clone(), state_clone.clone())
let service = service_fn(move |req| {
let router = Arc::clone(&router_clone);
let state = Arc::clone(&state_clone);
async move { Ok::<_, Infallible>(router.handle(req, state).await) }
});

let result = Builder::new(TokioExecutor::new())
Expand All @@ -148,13 +78,3 @@ pub async fn start_server(
join_set.spawn(serve_connection);
}
}

async fn handle_request(
req: Request<Incoming>,
router: Arc<Router>,
state: SharedState,
) -> Result<Response<Full<Bytes>>, Infallible> {
let mut req = req;
req.extensions_mut().insert(state);
Ok(router.handle(req).await)
}
6 changes: 3 additions & 3 deletions examples/hyper/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use std::net::SocketAddr;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use tokio::net::TcpListener;
use wayfind_hyper_example::start_server;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
let addr: SocketAddr = "127.0.0.1:8000".parse()?;
let listener = TcpListener::bind(addr).await?;
let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8000);
let listener = TcpListener::bind(&socket).await?;
start_server(listener).await
}
22 changes: 22 additions & 0 deletions examples/hyper/src/response.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use bytes::Bytes;
use http::Response;
use http_body_util::Full;
use std::convert::Infallible;

pub type AppResponse = Response<Full<Bytes>>;

pub trait IntoResponse {
fn into_response(self) -> AppResponse;
}

impl IntoResponse for AppResponse {
fn into_response(self) -> AppResponse {
self
}
}

impl IntoResponse for Infallible {
fn into_response(self) -> AppResponse {
unreachable!()
}
}
Loading

0 comments on commit 634e10b

Please sign in to comment.