From fd9322923b31cc1c09e573ece80cf8cf3c19e1d4 Mon Sep 17 00:00:00 2001 From: soup Date: Sun, 2 Feb 2025 19:03:58 -0500 Subject: [PATCH] [shelves] Routing for rust port --- rs/Cargo.toml | 2 + rs/src/containers.rs | 71 ++++++++ rs/src/lib.rs | 2 + rs/src/router.rs | 82 +++++++++ shelves/Cargo.lock | 390 ++++++++++++++++++++++++++++++++++++++++ shelves/Cargo.toml | 2 +- shelves/backend/main.rs | 36 ++-- 7 files changed, 570 insertions(+), 15 deletions(-) create mode 100644 rs/src/containers.rs create mode 100644 rs/src/router.rs diff --git a/rs/Cargo.toml b/rs/Cargo.toml index e6a58be..ad4c64c 100644 --- a/rs/Cargo.toml +++ b/rs/Cargo.toml @@ -6,3 +6,5 @@ edition = "2024" [dependencies] rusqlite = { version = "*" } async-channel = { version = "*" } +urlpattern = { version = "*" } +url = { version = "*" } \ No newline at end of file diff --git a/rs/src/containers.rs b/rs/src/containers.rs new file mode 100644 index 0000000..b72fc97 --- /dev/null +++ b/rs/src/containers.rs @@ -0,0 +1,71 @@ +use core::fmt::Debug; +use std::borrow::Borrow; + +pub struct VecMap { + items: Vec<(K, V)>, +} + +impl Debug for VecMap +where + K: Debug, + V: Debug, +{ + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { + fmt.debug_map() + .entries(self.iter().map(|&(ref k, ref v)| (k, v))) + .finish() + } +} + +impl Default for VecMap { + fn default() -> Self { + Self::new() + } +} +impl VecMap { + pub fn new() -> Self { + Self { items: vec![] } + } + + pub fn iter(&self) -> impl Iterator { + self.items.iter() + } +} + +impl VecMap +where + K: Eq, +{ + pub fn get_mut(&mut self, k: &Q) -> Option<&mut V> + where + K: Borrow, + Q: Eq + ?Sized, + { + self.items + .iter_mut() + .find(|(ki, _)| ki.borrow() == k) + .map(|(_, v)| v) + } + + pub fn get(&self, k: &Q) -> Option<&V> + where + K: Borrow, + Q: Eq + ?Sized, + { + self.items + .iter() + .find(|(ki, _)| ki.borrow() == k) + .map(|(_, v)| v) + } + + pub fn insert(&mut self, k: K, mut v: V) -> Option { + if let Some(vi) = self.get_mut(&k) { + core::mem::swap(vi, &mut v); + + return Some(v); + } + + self.items.push((k, v)); + None + } +} diff --git a/rs/src/lib.rs b/rs/src/lib.rs index 8c65e5a..4883262 100644 --- a/rs/src/lib.rs +++ b/rs/src/lib.rs @@ -1,3 +1,5 @@ +pub mod containers; +pub mod router; pub mod rusqlite_thread_pool; pub fn add(left: u64, right: u64) -> u64 { diff --git a/rs/src/router.rs b/rs/src/router.rs new file mode 100644 index 0000000..012675d --- /dev/null +++ b/rs/src/router.rs @@ -0,0 +1,82 @@ +use crate::containers::VecMap; +use std::collections::HashMap; +use url::Url; +use urlpattern::{UrlPattern, UrlPatternInit, UrlPatternMatchInput}; + +pub type PathParams = HashMap>; + +struct UrlPatternWithInput { + pathname: String, + pattern: UrlPattern, +} +impl PartialEq for UrlPatternWithInput { + fn eq(&self, other: &Self) -> bool { + self.pathname == other.pathname + } +} +impl Eq for UrlPatternWithInput { +} +pub struct Router { + patterns: VecMap>, +} + +impl Router { + pub fn new() -> Self { + Self { + patterns: VecMap::new(), + } + } + + pub fn register(&mut self, method: String, pathname: String, value: T) { + let upwi = UrlPatternWithInput { + pathname: pathname.clone(), + pattern: UrlPattern::parse( + UrlPatternInit { + pathname: Some(pathname), + ..Default::default() + }, + Default::default(), + ) + .unwrap(), + }; + + if let Some(methods) = self.patterns.get_mut(&upwi) { + methods.insert(method, value); + } else { + let mut methods = VecMap::new(); + methods.insert(method, value); + self.patterns.insert(upwi, methods); + } + } + + pub fn get( + &self, + method: &str, + url: &str, + ) -> Result<(&T, PathParams), u16> { + let url = match Url::parse(url) { + Ok(u) => u, + Err(_) => match Url::parse(&format!("http://example.com{url}")) { + Ok(u) => u, + Err(_) => return Err(400), + }, + }; + + for (upwi, methods) in self.patterns.iter() { + let pattern = &upwi.pattern; + let result = pattern.exec(UrlPatternMatchInput::Url(url.clone())); + let pathGroups = match result { + Ok(Some(res)) => res.pathname.groups, + _ => continue, + }; + + if let Some(t) = methods.get(method) { + return Ok((t, pathGroups)); + } + + return Err(405); + } + + Err(404) + } +} diff --git a/shelves/Cargo.lock b/shelves/Cargo.lock index 23b45f5..b1c5745 100644 --- a/shelves/Cargo.lock +++ b/shelves/Cargo.lock @@ -17,6 +17,15 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "async-channel" version = "2.3.1" @@ -35,6 +44,8 @@ version = "0.0.0" dependencies = [ "async-channel", "rusqlite", + "url", + "urlpattern", ] [[package]] @@ -44,6 +55,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8" dependencies = [ "axum-core", + "axum-macros", "bytes", "form_urlencoded", "futures-util", @@ -91,6 +103,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "backtrace" version = "0.3.74" @@ -148,6 +171,17 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "event-listener" version = "5.4.0" @@ -340,6 +374,145 @@ dependencies = [ "tower-service", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "itoa" version = "1.0.14" @@ -363,6 +536,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + [[package]] name = "log" version = "0.4.25" @@ -470,6 +649,35 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + [[package]] name = "rusqlite" version = "0.33.0" @@ -589,6 +797,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "syn" version = "2.0.98" @@ -606,6 +820,27 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tokio" version = "1.43.0" @@ -680,12 +915,88 @@ dependencies = [ "once_cell", ] +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-ucd-ident" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + [[package]] name = "unicode-ident" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "urlpattern" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +dependencies = [ + "regex", + "serde", + "unic-ucd-ident", + "url", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "vcpkg" version = "0.2.15" @@ -770,3 +1081,82 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/shelves/Cargo.toml b/shelves/Cargo.toml index c00cabd..2322a6f 100644 --- a/shelves/Cargo.toml +++ b/shelves/Cargo.toml @@ -9,7 +9,7 @@ path = "backend/main.rs" [dependencies] tokio = { version = "1.43.0", features = ["rt-multi-thread", "fs", "net"] } -axum = { version = "0.8.1", features = ["http1"] } +axum = { version = "0.8.1", features = ["http1", "macros"] } http = "1.2.0" atelier = { path = "../rs" } diff --git a/shelves/backend/main.rs b/shelves/backend/main.rs index cb9a755..f6d5609 100644 --- a/shelves/backend/main.rs +++ b/shelves/backend/main.rs @@ -1,4 +1,6 @@ use std::net::SocketAddr; +use std::pin::Pin; +use std::sync::Arc; use axum::handler::HandlerWithoutStateExt; use axum::response::IntoResponse; @@ -20,17 +22,22 @@ fn init(connection: &mut rusqlite::Connection) -> rusqlite::Result<()> { type Request = axum::extract::Request; type Response = axum::response::Response; -type Handler = Box Box>>; +type Handler = Box< + dyn Fn(Request) -> Pin + Send + 'static>> + + Send + + Sync + + 'static, +>; type Router = atelier::router::Router; fn make_handler(f: F) -> Handler where - Fut: Future, - F: FnMut(Request) -> Fut + Clone + 'static, + Fut: Future + Send + 'static, + F: FnMut(Request) -> Fut + Clone + Send + Sync + 'static, { Box::new(move |req| { let mut f = f.clone(); - Box::new(async move { f(req).await }) + Box::pin(async move { f(req).await }) }) } @@ -43,26 +50,26 @@ fn make_router() -> Router { macro_rules! r { ($m:expr, $p:expr, $h:expr) => { - router.register($m, $p, make_handler($h)) + router.register($m.to_string(), $p.to_string(), make_handler($h)) }; } - r!("get", "/hello", get_hello); + r!("GET", "/hello", get_hello); router } async fn serve( dbs: Dbs, - router: &Router, + router: Arc, req: axum::extract::Request, ) -> axum::response::Response { let method = req.method().as_str(); let uri = format!("{}", req.uri()); - let handler = match router.get(method, uri) { - (Some(h), _) => h, - (None, Some(sc)) => { + let (handler, pathParams) = match router.get(method, &uri) { + Ok(h) => h, + Err(sc) => { return (http::status::StatusCode::from_u16(sc).unwrap(), "") .into_response() }, @@ -85,14 +92,15 @@ async fn go() -> Result<()> { let addr = "127.0.0.1:8333".parse::()?; let listener = TcpListener::bind(addr).await?; - let router = make_router(); - let serve = move |req| { + let router = Arc::new(make_router()); + let s = move |req: Request| { let tx = tx.clone(); + let router = router.clone(); - serve(tx, router, req) + serve(tx, router.clone(), req) }; - axum::serve(listener, serve.into_make_service()).await?; + axum::serve(listener, s.into_make_service()).await?; Ok(()) }