From 634e10b4e6efac62d7244d047cdf6d82b9064125 Mon Sep 17 00:00:00 2001 From: Cathal Mullan Date: Mon, 2 Sep 2024 15:20:08 +0100 Subject: [PATCH] Setup axum-like traits --- Cargo.lock | 363 ++++++++++++---------------- examples/hyper/Cargo.toml | 9 +- examples/hyper/README.md | 2 - examples/hyper/src/extract.rs | 59 +++++ examples/hyper/src/extract/path.rs | 14 ++ examples/hyper/src/handler.rs | 94 +++++++ examples/hyper/src/lib.rs | 122 ++-------- examples/hyper/src/main.rs | 6 +- examples/hyper/src/response.rs | 22 ++ examples/hyper/src/router.rs | 78 ++++++ examples/hyper/src/routes/delete.rs | 10 +- examples/hyper/src/routes/get.rs | 10 +- examples/hyper/src/routes/list.rs | 9 +- examples/hyper/src/routes/set.rs | 20 +- examples/hyper/src/routes/wipe.rs | 8 +- examples/hyper/tests/e2e.rs | 8 +- 16 files changed, 480 insertions(+), 354 deletions(-) create mode 100644 examples/hyper/src/extract.rs create mode 100644 examples/hyper/src/extract/path.rs create mode 100644 examples/hyper/src/handler.rs create mode 100644 examples/hyper/src/response.rs create mode 100644 examples/hyper/src/router.rs diff --git a/Cargo.lock b/Cargo.lock index cad6b760..d45d1366 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -104,6 +104,12 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.7.1" @@ -245,22 +251,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - [[package]] name = "criterion" version = "0.5.1" @@ -365,15 +355,6 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" -[[package]] -name = "encoding_rs" -version = "0.8.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" -dependencies = [ - "cfg-if", -] - [[package]] name = "equivalent" version = "1.0.1" @@ -390,33 +371,12 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "fastrand" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" - [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.1" @@ -622,22 +582,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", + "webpki-roots", ] [[package]] @@ -832,23 +777,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "native-tls" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "ntex-bytes" version = "0.1.27" @@ -904,50 +832,6 @@ version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" -[[package]] -name = "openssl" -version = "0.10.66" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "parking_lot" version = "0.12.3" @@ -1018,12 +902,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkg-config" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" - [[package]] name = "plotters" version = "0.3.6" @@ -1052,6 +930,15 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro2" version = "1.0.86" @@ -1061,6 +948,54 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quinn" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b22d8e7369034b9a7132bc2008cac12f2013c8132b45e0554e6e20e2617f2156" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba92fb39ec7ad06ca2582c0ca834dfeadcaf06ddfc8e635c80aa7e1c05315fdd" +dependencies = [ + "bytes", + "rand", + "ring", + "rustc-hash", + "rustls", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" +dependencies = [ + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "quote" version = "1.0.36" @@ -1070,6 +1005,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "rayon" version = "1.10.0" @@ -1142,38 +1107,37 @@ checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" dependencies = [ "base64", "bytes", - "encoding_rs", "futures-core", "futures-util", - "h2", "http 1.1.0", "http-body", "http-body-util", "hyper", "hyper-rustls", - "hyper-tls", "hyper-util", "ipnet", "js-sys", "log", "mime", - "native-tls", "once_cell", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", "rustls-pemfile", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", - "system-configuration", "tokio", - "tokio-native-tls", + "tokio-rustls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots", "windows-registry", ] @@ -1214,6 +1178,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + [[package]] name = "rustix" version = "0.38.34" @@ -1234,6 +1204,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" dependencies = [ "once_cell", + "ring", "rustls-pki-types", "rustls-webpki", "subtle", @@ -1282,44 +1253,12 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "schannel" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" -dependencies = [ - "windows-sys 0.52.0", -] - [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "serde" version = "1.0.208" @@ -1469,47 +1408,33 @@ dependencies = [ ] [[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" +name = "terminal_size" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "core-foundation-sys", - "libc", + "rustix", + "windows-sys 0.48.0", ] [[package]] -name = "tempfile" -version = "3.12.0" +name = "thiserror" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ - "cfg-if", - "fastrand", - "once_cell", - "rustix", - "windows-sys 0.59.0", + "thiserror-impl", ] [[package]] -name = "terminal_size" -version = "0.3.0" +name = "thiserror-impl" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ - "rustix", - "windows-sys 0.48.0", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1566,16 +1491,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.0" @@ -1691,12 +1606,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.5" @@ -1847,6 +1756,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "winapi-util" version = "0.1.9" @@ -2049,6 +1967,27 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "467b95b08735dcd7061be626d02aea062bc0b99918bc9de11b8fc15d901158ae" +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zeroize" version = "1.8.1" diff --git a/examples/hyper/Cargo.toml b/examples/hyper/Cargo.toml index 35bc74d7..c7323446 100644 --- a/examples/hyper/Cargo.toml +++ b/examples/hyper/Cargo.toml @@ -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", +] } diff --git a/examples/hyper/README.md b/examples/hyper/README.md index 398bde38..81b1e283 100644 --- a/examples/hyper/README.md +++ b/examples/hyper/README.md @@ -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: diff --git a/examples/hyper/src/extract.rs b/examples/hyper/src/extract.rs new file mode 100644 index 00000000..c7da3b2e --- /dev/null +++ b/examples/hyper/src/extract.rs @@ -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; + +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; +} + +impl FromRequestParts for SharedAppState { + type Rejection = Infallible; + + fn from_request_parts(_: &mut Parts, state: &SharedAppState) -> Result { + Ok(state.clone()) + } +} + +pub trait FromRequest: Sized { + type Rejection: IntoResponse; + + fn from_request(req: AppRequest, state: &SharedAppState) -> Result; +} + +impl FromRequest for T +where + T: FromRequestParts, +{ + type Rejection = ::Rejection; + + fn from_request(req: AppRequest, state: &SharedAppState) -> Result { + 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 { + Ok(req) + } +} diff --git a/examples/hyper/src/extract/path.rs b/examples/hyper/src/extract/path.rs new file mode 100644 index 00000000..1aaa7cbf --- /dev/null +++ b/examples/hyper/src/extract/path.rs @@ -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 { + Ok(Self(parts.uri.path().to_string())) + } +} diff --git a/examples/hyper/src/handler.rs b/examples/hyper/src/handler.rs new file mode 100644 index 00000000..b594385e --- /dev/null +++ b/examples/hyper/src/handler.rs @@ -0,0 +1,94 @@ +use crate::{ + extract::{AppRequest, FromRequest, FromRequestParts}, + response::{AppResponse, IntoResponse}, + SharedAppState, +}; +use std::{future::Future, pin::Pin}; + +pub trait Handler: Clone + Send + Sized + 'static { + type Future: Future + Send + 'static; + + fn call(self, req: AppRequest, state: SharedAppState) -> Self::Future; +} + +impl Handler<()> for F +where + F: FnOnce() -> Fut + Clone + Send + 'static, + Fut: Future + Send, + Res: IntoResponse, +{ + type Future = Pin + 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 Handler<(M, $($ty,)* $last,)> for F + where + F: FnOnce($($ty,)* $last,) -> Fut + Clone + Send + 'static, + Fut: Future + Send, + Res: IntoResponse, + $( $ty: FromRequestParts + Send, )* + $( <$ty as FromRequestParts>::Rejection: IntoResponse, )* + $last: FromRequest + Send, + <$last as FromRequest>::Rejection: IntoResponse, + { + type Future = Pin + 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); diff --git a/examples/hyper/src/lib.rs b/examples/hyper/src/lib.rs index ed16ad5e..4d572ce3 100644 --- a/examples/hyper/src/lib.rs +++ b/examples/hyper/src/lib.rs @@ -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>; +pub type SharedAppState = Arc>; pub struct AppState { pub db: BTreeMap, } -pub type AppResponse = Response>; - -pub type HandlerFn = Arc< - dyn Fn(Request) -> Pin + Send>> + Send + Sync, ->; - -pub struct Router { - routes: HashMap>, -} - -impl Router { - #[must_use] - pub fn new() -> Self { - Self { - routes: HashMap::new(), - } - } - - pub fn route(&mut self, method: Method, path: &str, handler: F) -> &mut Self - where - F: Fn(Request) -> Fut + Send + Sync + 'static, - Fut: Future + Send + 'static, - { - let handler = Arc::new(move |req| { - Box::pin(handler(req)) as Pin + Send>> - }); - - self.routes - .entry(method) - .or_default() - .insert(path, handler) - .unwrap(); - - self - } - - pub async fn handle(&self, req: Request) -> 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> { 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) @@ -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()) @@ -148,13 +78,3 @@ pub async fn start_server( join_set.spawn(serve_connection); } } - -async fn handle_request( - req: Request, - router: Arc, - state: SharedState, -) -> Result>, Infallible> { - let mut req = req; - req.extensions_mut().insert(state); - Ok(router.handle(req).await) -} diff --git a/examples/hyper/src/main.rs b/examples/hyper/src/main.rs index 15a965b4..48db91f9 100644 --- a/examples/hyper/src/main.rs +++ b/examples/hyper/src/main.rs @@ -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> { - 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 } diff --git a/examples/hyper/src/response.rs b/examples/hyper/src/response.rs new file mode 100644 index 00000000..5f2d3e70 --- /dev/null +++ b/examples/hyper/src/response.rs @@ -0,0 +1,22 @@ +use bytes::Bytes; +use http::Response; +use http_body_util::Full; +use std::convert::Infallible; + +pub type AppResponse = Response>; + +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!() + } +} diff --git a/examples/hyper/src/router.rs b/examples/hyper/src/router.rs new file mode 100644 index 00000000..0600fb84 --- /dev/null +++ b/examples/hyper/src/router.rs @@ -0,0 +1,78 @@ +use crate::{extract::AppRequest, handler::Handler, response::AppResponse, SharedAppState}; +use bytes::Bytes; +use http::{Method, Response, StatusCode}; +use http_body_util::Full; +use std::{collections::HashMap, future::Future, pin::Pin, sync::Arc}; + +type ArcHandler = Arc< + dyn Fn(AppRequest, SharedAppState) -> Pin + Send>> + + Send + + Sync, +>; + +pub struct AppRouter { + routes: HashMap>, +} + +impl AppRouter { + #[must_use] + pub fn new() -> Self { + Self { + routes: HashMap::new(), + } + } + + pub fn route(&mut self, method: Method, path: &str, handler: H) -> &mut Self + where + H: Handler + Send + Sync + 'static, + { + let handler: ArcHandler = Arc::new(move |req, state| { + let handler = handler.clone(); + Box::pin(async move { handler.call(req, state).await }) + }); + + self.routes + .entry(method) + .or_default() + .insert(path, handler) + .unwrap(); + + self + } + + pub async fn handle(&self, req: AppRequest, state: SharedAppState) -> 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, state).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 AppRouter { + fn default() -> Self { + Self::new() + } +} diff --git a/examples/hyper/src/routes/delete.rs b/examples/hyper/src/routes/delete.rs index e71f11c3..ea37adf5 100644 --- a/examples/hyper/src/routes/delete.rs +++ b/examples/hyper/src/routes/delete.rs @@ -1,8 +1,7 @@ -use crate::{AppResponse, SharedState}; +use crate::{extract::path::Path, response::IntoResponse, SharedAppState}; use bytes::Bytes; -use http::{Request, Response, StatusCode}; +use http::{Response, StatusCode}; use http_body_util::Full; -use hyper::body::Incoming; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] @@ -11,9 +10,8 @@ pub struct DeleteResponse { pub key: String, } -pub async fn handle_delete(req: Request) -> AppResponse { - let key = req.uri().path().trim_start_matches('/').to_string(); - let state = req.extensions().get::().unwrap().clone(); +pub async fn handle_delete(state: SharedAppState, Path(path): Path) -> impl IntoResponse { + let key = path.trim_start_matches('/').to_string(); state.write().await.db.remove(&key); let response = DeleteResponse { diff --git a/examples/hyper/src/routes/get.rs b/examples/hyper/src/routes/get.rs index fea70bf0..e0b2518e 100644 --- a/examples/hyper/src/routes/get.rs +++ b/examples/hyper/src/routes/get.rs @@ -1,8 +1,7 @@ -use crate::{AppResponse, SharedState}; +use crate::{extract::path::Path, response::IntoResponse, SharedAppState}; use bytes::Bytes; -use http::{Request, Response, StatusCode}; +use http::{Response, StatusCode}; use http_body_util::Full; -use hyper::body::Incoming; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] @@ -12,9 +11,8 @@ pub struct GetResponse { pub value: Option, } -pub async fn handle_get(req: Request) -> AppResponse { - let key = req.uri().path().trim_start_matches('/').to_string(); - let state = req.extensions().get::().unwrap().clone(); +pub async fn handle_get(state: SharedAppState, Path(path): Path) -> impl IntoResponse { + let key = path.trim_start_matches('/').to_string(); let db = &state.read().await.db; let Some(value) = db.get(&key) else { diff --git a/examples/hyper/src/routes/list.rs b/examples/hyper/src/routes/list.rs index 72036880..ae1f5aaa 100644 --- a/examples/hyper/src/routes/list.rs +++ b/examples/hyper/src/routes/list.rs @@ -1,8 +1,7 @@ -use crate::{AppResponse, SharedState}; +use crate::{response::IntoResponse, SharedAppState}; use bytes::Bytes; -use http::{Request, Response, StatusCode}; +use http::{Response, StatusCode}; use http_body_util::Full; -use hyper::body::Incoming; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] @@ -11,10 +10,8 @@ pub struct ListResponse { pub keys: Vec, } -pub async fn handle_list(req: Request) -> AppResponse { - let state = req.extensions().get::().unwrap().clone(); +pub async fn handle_list(state: SharedAppState) -> impl IntoResponse { let db = &state.read().await.db; - let keys: Vec = db.keys().cloned().collect(); let response = ListResponse { diff --git a/examples/hyper/src/routes/set.rs b/examples/hyper/src/routes/set.rs index 0c1ca431..76531bcb 100644 --- a/examples/hyper/src/routes/set.rs +++ b/examples/hyper/src/routes/set.rs @@ -1,8 +1,11 @@ -use crate::{AppResponse, SharedState}; +use crate::{ + extract::{path::Path, AppRequest}, + response::IntoResponse, + SharedAppState, +}; use bytes::Bytes; -use http::{Request, Response, StatusCode}; +use http::{Response, StatusCode}; use http_body_util::{BodyExt, Full}; -use hyper::body::Incoming; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] @@ -11,11 +14,14 @@ pub struct SetResponse { pub key: String, } -pub async fn handle_set(mut req: Request) -> AppResponse { - let key = req.uri().path().trim_start_matches('/').to_string(); - let state = req.extensions().get::().unwrap().clone(); +pub async fn handle_set( + state: SharedAppState, + Path(path): Path, + req: AppRequest, +) -> impl IntoResponse { + let key = path.trim_start_matches('/').to_string(); - let body = req.body_mut().collect().await.unwrap().to_bytes(); + let body = req.collect().await.unwrap().to_bytes(); state.write().await.db.insert(key.clone(), body); let response = SetResponse { diff --git a/examples/hyper/src/routes/wipe.rs b/examples/hyper/src/routes/wipe.rs index b838f8af..fe728f46 100644 --- a/examples/hyper/src/routes/wipe.rs +++ b/examples/hyper/src/routes/wipe.rs @@ -1,8 +1,7 @@ -use crate::{AppResponse, SharedState}; +use crate::{response::IntoResponse, SharedAppState}; use bytes::Bytes; -use http::{Request, Response, StatusCode}; +use http::{Response, StatusCode}; use http_body_util::Full; -use hyper::body::Incoming; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] @@ -10,8 +9,7 @@ pub struct WipeResponse { pub message: String, } -pub async fn handle_wipe(req: Request) -> AppResponse { - let state = req.extensions().get::().unwrap().clone(); +pub async fn handle_wipe(state: SharedAppState) -> impl IntoResponse { state.write().await.db.clear(); let response = WipeResponse { diff --git a/examples/hyper/tests/e2e.rs b/examples/hyper/tests/e2e.rs index 97119687..0cd5be07 100644 --- a/examples/hyper/tests/e2e.rs +++ b/examples/hyper/tests/e2e.rs @@ -1,17 +1,17 @@ use reqwest::{Client, StatusCode}; -use std::net::SocketAddr; -use tokio::sync::oneshot; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use tokio::{net::TcpListener, sync::oneshot}; use wayfind_hyper_example::{ routes::{delete::DeleteResponse, list::ListResponse, set::SetResponse, wipe::WipeResponse}, start_server, }; async fn start_test_server() -> SocketAddr { - let addr: SocketAddr = "127.0.0.1:0".parse().unwrap(); + let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0); let (tx, rx) = oneshot::channel(); tokio::spawn(async move { - let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); + let listener = TcpListener::bind(&socket).await.unwrap(); let local_addr = listener.local_addr().unwrap(); tx.send(local_addr).unwrap(); start_server(listener).await.unwrap();