diff --git a/Cargo.lock b/Cargo.lock index a3556dc..50dc4c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -479,18 +479,25 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" -[[package]] -name = "ecow" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78e4f79b296fbaab6ce2e22d52cb4c7f010fe0ebe7a32e34fa25885fd797bd02" - [[package]] name = "endian-type" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "869b0adbda23651a9c5c0c3d270aac9fcb52e8622a8f2b17e57802d7791962f2" +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -829,6 +836,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + [[package]] name = "hyper" version = "1.8.1" @@ -984,6 +997,17 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "is-terminal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -1074,6 +1098,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "matchit" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f926ade0c4e170215ae43342bf13b9310a437609c81f29f86c5df6657582ef9" + [[package]] name = "memchr" version = "2.7.6" @@ -1652,6 +1682,24 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + [[package]] name = "sha1" version = "0.6.1" @@ -1770,13 +1818,17 @@ version = "0.0.3" dependencies = [ "bytes", "clap", + "env_logger", "http", "http-body-util", "hyper", "hyper-util", "log", + "matchit", + "serde", "thiserror 2.0.18", "tokio", + "toml 0.7.8", "vetis", ] @@ -1889,6 +1941,15 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "termtree" version = "0.5.1" @@ -2048,6 +2109,79 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit", +] + +[[package]] +name = "toml" +version = "0.9.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned 1.0.4", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.14", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "winnow 0.5.40", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ + "winnow 0.7.14", +] + +[[package]] +name = "toml_writer" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" + [[package]] name = "tower-service" version = "0.3.3" @@ -2131,13 +2265,10 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "vetis" version = "0.1.4-beta.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd111db96fd2dc8e21dff5e32d5193ecf80cb586a46344480a05ccf3df2d5b6" dependencies = [ "async-signal", "bytes", "deboa", - "ecow", "futures-lite", "futures-rustls", "h3", @@ -2164,6 +2295,7 @@ dependencies = [ "thiserror 2.0.18", "tokio", "tokio-rustls", + "toml 0.9.11+spec-1.1.0", "url", ] @@ -2533,6 +2665,21 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" + [[package]] name = "wit-bindgen" version = "0.51.0" diff --git a/Cargo.toml b/Cargo.toml index 1206584..b839b12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,5 +34,9 @@ hyper = { version = "1.8.1", features = ["server"] } hyper-util = "0.1.19" log = "0.4.29" thiserror = "2.0.18" -vetis = "0.1.4-beta.1" +vetis = { version = "0.1.4-beta.1", path = "../vetis/vetis" } tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } +toml = "0.7.6" +serde = "1.0.228" +env_logger = "0.10.2" +matchit = "0.8.6" diff --git a/examples/jsonmock/Cargo.lock b/examples/jsonmock/Cargo.lock index 88a80d8..101300e 100644 --- a/examples/jsonmock/Cargo.lock +++ b/examples/jsonmock/Cargo.lock @@ -162,9 +162,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.5.54" +version = "4.5.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e" dependencies = [ "clap_builder", "clap_derive", @@ -172,9 +172,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.54" +version = "4.5.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0" dependencies = [ "anstream", "anstyle", @@ -184,9 +184,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.49" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" dependencies = [ "heck", "proc-macro2", @@ -326,12 +326,6 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" -[[package]] -name = "ecow" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78e4f79b296fbaab6ce2e22d52cb4c7f010fe0ebe7a32e34fa25885fd797bd02" - [[package]] name = "endian-type" version = "0.2.0" @@ -348,6 +342,19 @@ dependencies = [ "regex", ] +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + [[package]] name = "env_logger" version = "0.11.8" @@ -401,12 +408,6 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "form_urlencoded" version = "1.2.2" @@ -568,25 +569,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "h2" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "hashbrown" version = "0.16.1" @@ -599,6 +581,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "http" version = "1.4.0" @@ -644,6 +632,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + [[package]] name = "hyper" version = "1.8.1" @@ -654,7 +648,6 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2", "http", "http-body", "httparse", @@ -800,6 +793,17 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "is-terminal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -883,7 +887,7 @@ name = "jsonmock" version = "0.1.0" dependencies = [ "bytes", - "env_logger", + "env_logger 0.11.8", "http-body-util", "hyper", "sofie", @@ -920,6 +924,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "matchit" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f926ade0c4e170215ae43342bf13b9310a437609c81f29f86c5df6657582ef9" + [[package]] name = "memchr" version = "2.7.6" @@ -1472,6 +1482,24 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + [[package]] name = "sha1" version = "0.6.1" @@ -1537,13 +1565,17 @@ version = "0.0.3" dependencies = [ "bytes", "clap", + "env_logger 0.10.2", "http", "http-body-util", "hyper", "hyper-util", "log", + "matchit", + "serde", "thiserror 2.0.18", "tokio", + "toml 0.7.8", "vetis", ] @@ -1656,6 +1688,15 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "termtree" version = "0.5.1" @@ -1815,6 +1856,79 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit", +] + +[[package]] +name = "toml" +version = "0.9.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned 1.0.4", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.14", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "winnow 0.5.40", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ + "winnow 0.7.14", +] + +[[package]] +name = "toml_writer" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" + [[package]] name = "tower-service" version = "0.3.3" @@ -1898,12 +2012,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "vetis" version = "0.1.4-beta.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd111db96fd2dc8e21dff5e32d5193ecf80cb586a46344480a05ccf3df2d5b6" dependencies = [ "bytes", "deboa", - "ecow", "futures-lite", "futures-rustls", "http", @@ -1923,6 +2034,7 @@ dependencies = [ "thiserror 2.0.18", "tokio", "tokio-rustls", + "toml 0.9.11+spec-1.1.0", "url", ] @@ -2292,6 +2404,21 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" + [[package]] name = "wit-bindgen" version = "0.51.0" diff --git a/examples/jsonmock/sofie.toml b/examples/jsonmock/sofie.toml new file mode 100644 index 0000000..8c0378c --- /dev/null +++ b/examples/jsonmock/sofie.toml @@ -0,0 +1,2 @@ +port = 8080 +interface = "0.0.0.0" \ No newline at end of file diff --git a/examples/jsonmock/src/main.rs b/examples/jsonmock/src/main.rs index 37ca2ff..08d727b 100644 --- a/examples/jsonmock/src/main.rs +++ b/examples/jsonmock/src/main.rs @@ -1,17 +1,15 @@ -use sofie::App; -use http_body_util::{Full}; -use bytes::Bytes; -use hyper::Response; +use sofie::{ + App, Response, +}; #[tokio::main] async fn main() -> Result<(), Box> { - std_logger::Config::logfmt().init(); + env_logger::Builder::from_env(env_logger::Env::default().filter_or("RUST_LOG", "info")).init(); - let mut app = App::new(); + let mut app = App::default(); - app.serve(|_| async move { - Ok(Response::new(Full::new(Bytes::from("Hello World")))) - }).await?; + app.serve(|req| async move { Ok(Response::builder().text("Hello World")) }) + .await?; Ok(()) } diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..970aea5 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,35 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct Config { + port: u16, + interface: String, + security: Option, +} + +impl Default for Config { + fn default() -> Self { + Config { port: 5000, interface: "0.0.0.0".to_string(), security: None } + } +} + +impl Config { + pub fn port(&self) -> u16 { + self.port + } + + pub fn interface(&self) -> &str { + &self.interface + } + + pub fn security(&self) -> Option<&SecurityConfig> { + self.security + .as_ref() + } +} + +#[derive(Deserialize)] +pub struct SecurityConfig { + cert_path: String, + key_path: String, +} diff --git a/src/lib.rs b/src/lib.rs index decd77b..2d21c03 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -use std::{env, future::Future}; +use std::{fs::read_to_string, future::Future, path::Path}; use log::error; use vetis::{ @@ -8,53 +8,92 @@ use vetis::{ path::HandlerPath, virtual_host::{handler_fn, VirtualHost}, }, - Request, Response, Vetis, + Vetis, }; -use crate::errors::SofieError; +use crate::{config::Config, errors::SofieError}; +pub static CONFIG: &str = "sofie.toml"; + +pub mod config; pub mod errors; mod tests; -pub struct App {} +pub type Request = vetis::Request; +pub type Response = vetis::Response; -impl App { - pub fn new() -> App { - App {} - } +pub struct App { + config: Config, + server: vetis::Vetis, +} - pub async fn serve(&mut self, handler: F) -> Result<(), SofieError> - where - F: Fn(Request) -> Fut + Send + Sync + 'static, - Fut: Future> + Send + Sync + 'static, - { - let port = env::var("PORT") - .unwrap_or("8080".to_string()) - .parse::() - .unwrap(); - let interface = env::var("INTERFACE").unwrap_or("0.0.0.0".to_string()); +impl Default for App { + fn default() -> Self { + let config = if Path::exists(Path::new(CONFIG)) { + let file = read_to_string(CONFIG); + if let Ok(file) = file { + let config = toml::from_str(&file); + if let Ok(config) = config { + config + } else { + error!("Failed to parse config file"); + Config::default() + } + } else { + Config::default() + } + } else { + Config::default() + }; + + let port = config.port(); + let interface = config.interface(); let listener_config = ListenerConfig::builder() .port(port) - .interface(&interface) + .interface(interface) .build(); - let config = ServerConfig::builder() + let server_config = ServerConfig::builder() .add_listener(listener_config) .build(); + App { config, server: Vetis::new(server_config) } + } +} + +impl App { + pub async fn serve(&mut self, handler: H) -> Result<(), SofieError> + where + H: Fn(Request) -> Fut + Clone + Send + Sync + 'static, + Fut: Future> + Send + Sync + 'static, + { let localhost_config = VirtualHostConfig::builder() .hostname("localhost") + .port(self.config.port()) .build() .map_err(|e| SofieError::ServerStart(e.to_string()))?; let mut virtual_host = VirtualHost::new(localhost_config); - virtual_host.add_path(HandlerPath::new_host_path("/", handler_fn(handler))); + virtual_host.add_path( + HandlerPath::builder() + .uri("/") + .handler(handler_fn(move |_req| { + let value = handler.clone(); + async move { value(_req).await } + })) + .build() + .map_err(|e| SofieError::ServerStart(e.to_string()))?, + ); - let mut server = Vetis::new(config); - server.add_virtual_host(virtual_host); + self.server + .add_virtual_host(virtual_host) + .await; - let result = server.run().await; + let result = self + .server + .run() + .await; if let Err(e) = result { error!("Failed to start server: {}", e); diff --git a/src/tests/app.rs b/src/tests/app.rs new file mode 100644 index 0000000..ef8703d --- /dev/null +++ b/src/tests/app.rs @@ -0,0 +1,84 @@ +#[cfg(test)] +mod sofie_tests { + use crate::App; + + #[test] + fn test_sophia_send_sync() { + fn assert_send_sync() {} + assert_send_sync::(); + } + + #[test] + fn test_sophia_clone() { + let sophia1 = App::default(); + let _sophia2 = sophia1; + let _sophia3 = App::default(); + } +} + +#[cfg(test)] +mod sophia_integration_tests { + use vetis::config::{ListenerConfig, ServerConfig}; + + use crate::App; + + #[tokio::test] + async fn test_sophia_handler_signature() { + async fn test_handler( + _req: vetis::Request, + ) -> Result> { + // Return a simple error since we can't create ResponseType easily + Err("Test error".into()) + } + + let _handler = test_handler; + + assert!(true); + } + + #[tokio::test] + async fn test_sophia_configuration() { + let mut sophia = App::default(); + + let listener_config = ListenerConfig::builder() + .port(8080) + .interface("127.0.0.1") + .build(); + + let config = ServerConfig::builder() + .add_listener(listener_config) + .build(); + + assert_eq!( + config + .listeners() + .len(), + 1 + ); + assert_eq!(config.listeners()[0].port(), 8080); + assert_eq!(config.listeners()[0].interface(), "127.0.0.1"); + + let _ = &mut sophia; + assert!(true); + } + + #[tokio::test] + async fn test_sophia_error_handling() { + use crate::errors::SofieError; + + let error = SofieError::ServerStart("Test error".to_string()); + + let display_str = format!("{}", error); + assert!(display_str.contains("Failed to start server")); + assert!(display_str.contains("Test error")); + + let debug_str = format!("{:?}", error); + assert!(debug_str.contains("ServerStart")); + + let error2 = SofieError::ServerStart("Test error".to_string()); + assert_eq!(error, error2); + + let error3 = SofieError::ServerStart("Different error".to_string()); + assert_ne!(error, error3); + } +} diff --git a/src/tests/config.rs b/src/tests/config.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/tests/config.rs @@ -0,0 +1 @@ + diff --git a/src/tests/handler.rs b/src/tests/handler.rs index c3b5c17..fac9752 100644 --- a/src/tests/handler.rs +++ b/src/tests/handler.rs @@ -1,35 +1,37 @@ #[cfg(test)] mod handler_tests { - use bytes::Bytes; - use http_body_util::Full; - use hyper::{Response, StatusCode}; - - type TestResponseType = Response>; + use http::HeaderValue; + use hyper::StatusCode; + use vetis::Response; #[tokio::test] async fn test_response_creation() { - // Test that we can create basic HTTP responses - let response: TestResponseType = Response::builder() + let response: Response = Response::builder() .status(StatusCode::OK) - .body(Full::new(Bytes::from("Hello, World!"))) - .unwrap(); + .text("Hello, World!"); - assert_eq!(response.status(), StatusCode::OK); + assert_eq!( + response + .into_inner() + .status(), + StatusCode::OK + ); } #[tokio::test] async fn test_json_response() { let json_data = r#"{"message": "Hello from Sophia", "status": "success"}"#; - let response: TestResponseType = Response::builder() + let response: Response = Response::builder() .status(StatusCode::OK) - .header("Content-Type", "application/json") - .body(Full::new(Bytes::from(json_data))) - .unwrap(); + .header("Content-Type", HeaderValue::from_static("application/json")) + .text(json_data); - assert_eq!(response.status(), StatusCode::OK); + let inner_response = response.into_inner(); + + assert_eq!(inner_response.status(), StatusCode::OK); assert_eq!( - response + inner_response .headers() .get("Content-Type") .unwrap(), @@ -39,33 +41,38 @@ mod handler_tests { #[tokio::test] async fn test_error_response() { - let response: TestResponseType = Response::builder() + let response: Response = Response::builder() .status(StatusCode::INTERNAL_SERVER_ERROR) - .body(Full::new(Bytes::from("Internal Server Error"))) - .unwrap(); + .text("Internal Server Error"); - assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); + assert_eq!( + response + .into_inner() + .status(), + StatusCode::INTERNAL_SERVER_ERROR + ); } #[tokio::test] async fn test_response_with_headers() { - let response: TestResponseType = Response::builder() + let response: Response = Response::builder() .status(StatusCode::OK) - .header("X-Custom-Header", "test-value") - .header("Cache-Control", "no-cache") - .body(Full::new(Bytes::from("Response with headers"))) - .unwrap(); + .header("X-Custom-Header", HeaderValue::from_static("test-value")) + .header("Cache-Control", HeaderValue::from_static("no-cache")) + .text("Response with headers"); + + let inner_response = response.into_inner(); - assert_eq!(response.status(), StatusCode::OK); + assert_eq!(inner_response.status(), StatusCode::OK); assert_eq!( - response + inner_response .headers() .get("X-Custom-Header") .unwrap(), "test-value" ); assert_eq!( - response + inner_response .headers() .get("Cache-Control") .unwrap(), @@ -75,12 +82,13 @@ mod handler_tests { #[tokio::test] async fn test_empty_response() { - let response: TestResponseType = Response::builder() + let response: Response = Response::builder() .status(StatusCode::NO_CONTENT) - .body(Full::new(Bytes::new())) - .unwrap(); + .text(""); + + let inner_response = response.into_inner(); - assert_eq!(response.status(), StatusCode::NO_CONTENT); + assert_eq!(inner_response.status(), StatusCode::NO_CONTENT); } #[tokio::test] @@ -98,12 +106,13 @@ mod handler_tests { ]; for status in status_codes { - let response: TestResponseType = Response::builder() + let response: Response = Response::builder() .status(status) - .body(Full::new(Bytes::from("Test response"))) - .unwrap(); + .text("Test response"); - assert_eq!(response.status(), status); + let inner_response = response.into_inner(); + + assert_eq!(inner_response.status(), status); } } @@ -111,23 +120,29 @@ mod handler_tests { async fn test_large_response() { let large_data = "x".repeat(100_000); // 100KB of data - let response: TestResponseType = Response::builder() + let response: Response = Response::builder() .status(StatusCode::OK) .header( "Content-Length", - large_data - .len() - .to_string(), + HeaderValue::from_str( + &large_data + .len() + .to_string(), + ) + .unwrap(), ) - .body(Full::new(Bytes::from(large_data))) - .unwrap(); + .text(&large_data); - assert_eq!(response.status(), StatusCode::OK); + assert_eq!( + response + .into_inner() + .status(), + StatusCode::OK + ); } #[tokio::test] async fn test_async_operations() { - // Test async operations that might happen in handlers let result = async { tokio::time::sleep(std::time::Duration::from_millis(10)).await; "Async result" @@ -139,65 +154,74 @@ mod handler_tests { #[tokio::test] async fn test_handler_function() { - // Test that we can define a function that could be used as a handler - async fn test_handler() -> TestResponseType { + async fn test_handler() -> Response { Response::builder() .status(StatusCode::OK) - .body(Full::new(Bytes::from("Handler response"))) - .unwrap() + .text("Handler response") } let response = test_handler().await; - assert_eq!(response.status(), StatusCode::OK); + assert_eq!( + response + .into_inner() + .status(), + StatusCode::OK + ); } } #[cfg(test)] mod handler_integration_tests { - use bytes::Bytes; - use http_body_util::Full; - use hyper::{Response, StatusCode}; - - type TestResponseType = Response>; + use http::HeaderValue; + use hyper::StatusCode; + use vetis::Response; #[tokio::test] async fn test_rest_api_patterns() { - // Test common REST API response patterns - - // Success response - let success_response: TestResponseType = Response::builder() + let success_response: Response = Response::builder() .status(StatusCode::OK) - .header("Content-Type", "application/json") - .body(Full::new(Bytes::from(r#"{"status": "success", "data": {}}"#))) - .unwrap(); - assert_eq!(success_response.status(), StatusCode::OK); + .header("Content-Type", HeaderValue::from_static("application/json")) + .text(r#"{"status": "success", "data": {}}"#); + assert_eq!( + success_response + .into_inner() + .status(), + StatusCode::OK + ); - // Created response - let created_response: TestResponseType = Response::builder() + let created_response: Response = Response::builder() .status(StatusCode::CREATED) - .header("Content-Type", "application/json") - .header("Location", "/api/users/123") - .body(Full::new(Bytes::from(r#"{"id": 123, "created": true}"#))) - .unwrap(); - assert_eq!(created_response.status(), StatusCode::CREATED); + .header("Content-Type", HeaderValue::from_static("application/json")) + .header("Location", HeaderValue::from_static("/api/users/123")) + .text(r#"{"id": 123, "created": true}"#); + assert_eq!( + created_response + .into_inner() + .status(), + StatusCode::CREATED + ); - // Error response - let error_response: TestResponseType = Response::builder() + let error_response: Response = Response::builder() .status(StatusCode::BAD_REQUEST) - .header("Content-Type", "application/json") - .body(Full::new(Bytes::from(r#"{"error": "Bad Request", "message": "Invalid input"}"#))) - .unwrap(); - assert_eq!(error_response.status(), StatusCode::BAD_REQUEST); + .header("Content-Type", HeaderValue::from_static("application/json")) + .text(r#"{"error": "Bad Request", "message": "Invalid input"}"#); + assert_eq!( + error_response + .into_inner() + .status(), + StatusCode::BAD_REQUEST + ); - // Not found response - let not_found_response: TestResponseType = Response::builder() + let not_found_response: Response = Response::builder() .status(StatusCode::NOT_FOUND) - .header("Content-Type", "application/json") - .body(Full::new(Bytes::from( - r#"{"error": "Not Found", "message": "Resource not found"}"#, - ))) - .unwrap(); - assert_eq!(not_found_response.status(), StatusCode::NOT_FOUND); + .header("Content-Type", HeaderValue::from_static("application/json")) + .text(r#"{"error": "Not Found", "message": "Resource not found"}"#); + assert_eq!( + not_found_response + .into_inner() + .status(), + StatusCode::NOT_FOUND + ); } #[tokio::test] @@ -210,15 +234,16 @@ mod handler_integration_tests { ]; for (content_type, body) in content_types { - let response: TestResponseType = Response::builder() + let response: Response = Response::builder() .status(StatusCode::OK) - .header("Content-Type", content_type) - .body(Full::new(Bytes::from(body))) - .unwrap(); + .header("Content-Type", HeaderValue::from_static(content_type)) + .text(body); - assert_eq!(response.status(), StatusCode::OK); + let inner_response = response.into_inner(); + + assert_eq!(inner_response.status(), StatusCode::OK); assert_eq!( - response + inner_response .headers() .get("Content-Type") .unwrap(), @@ -229,32 +254,31 @@ mod handler_integration_tests { #[tokio::test] async fn test_response_chaining() { - // Simulate a chain of operations that might happen in a real handler - async fn process_request() -> Result> { - // Simulate validation tokio::time::sleep(std::time::Duration::from_millis(1)).await; - // Simulate business logic tokio::time::sleep(std::time::Duration::from_millis(1)).await; Ok("Processed data".to_string()) } - async fn create_response(data: String) -> TestResponseType { + async fn create_response(data: String) -> Response { Response::builder() .status(StatusCode::OK) - .header("Content-Type", "application/json") - .body(Full::new(Bytes::from(format!(r#"{{"result": "{}"}}"#, data)))) - .unwrap() + .header("Content-Type", HeaderValue::from_static("application/json")) + .text(&format!(r#"{{"result": "{}"}}"#, data)) } - // Test the chain let data = process_request() .await .unwrap(); - let response: TestResponseType = create_response(data).await; + let response: Response = create_response(data).await; - assert_eq!(response.status(), StatusCode::OK); + assert_eq!( + response + .into_inner() + .status(), + StatusCode::OK + ); } } diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 0d4fc74..c223f49 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,3 +1,8 @@ +#[cfg(test)] +pub mod app; +#[cfg(test)] +pub mod config; +#[cfg(test)] pub mod error; +#[cfg(test)] pub mod handler; -pub mod sophia; diff --git a/src/tests/sophia.rs b/src/tests/sophia.rs deleted file mode 100644 index b0ee78b..0000000 --- a/src/tests/sophia.rs +++ /dev/null @@ -1,136 +0,0 @@ -#[cfg(test)] -mod sofie_tests { - use crate::App; - - #[test] - fn test_sofie_new() { - let _sofie = App::new(); - // Test that Sophia can be created successfully - // Since Sophia is a simple struct with no fields, this just verifies it doesn't panic - assert_eq!(std::mem::size_of::(), 0); - } - - #[test] - fn test_sophia_default() { - // Test that we can create multiple instances - let sophia1 = App::new(); - let sophia2 = App::new(); - - // Both should be the same size (zero-sized struct) - assert_eq!(std::mem::size_of_val(&sophia1), 0); - assert_eq!(std::mem::size_of_val(&sophia2), 0); - } - - #[test] - fn test_sophia_send_sync() { - // Test that sofie implements Send and Sync - fn assert_send_sync() {} - assert_send_sync::(); - } - - #[test] - fn test_sophia_clone() { - // Test that Sophia can be cloned (since it's zero-sized) - let sophia1 = App::new(); - let _sophia2 = sophia1; - // Sophia is automatically Copy because it's zero-sized - let _sophia3 = App::new(); // Create a new instance instead - } -} - -#[cfg(test)] -mod sophia_integration_tests { - use vetis::config::{ListenerConfig, ServerConfig}; - - use crate::App; - - #[tokio::test] - async fn test_sophia_handler_signature() { - // Test that we can define a handler with the correct signature - // This tests the type compatibility without actually running the server - - // Define a simple handler function that matches the expected signature - async fn test_handler( - _req: vetis::Request, - ) -> Result> { - // Return a simple error since we can't create ResponseType easily - Err("Test error".into()) - } - - // Verify the handler can be stored and called (type checking) - let _handler = test_handler; - - // This test passes if it compiles, which means the signature is correct - assert!(true); - } - - #[tokio::test] - async fn test_sophia_configuration() { - // Test that Sophia can be configured with different scenarios - let mut sophia = App::new(); - - let listener_config = ListenerConfig::builder() - .port(8080) - .interface("127.0.0.1") - .build(); - - let config = ServerConfig::builder() - .add_listener(listener_config) - .build(); - - // Verify the config was created successfully - assert_eq!( - config - .listeners() - .len(), - 1 - ); - assert_eq!(config.listeners()[0].port(), 8080); - assert_eq!(config.listeners()[0].interface(), "127.0.0.1"); - - // Test that Sophia can work with this configuration - // (We don't actually start the server to avoid port conflicts) - let _ = &mut sophia; - assert!(true); - } - - #[tokio::test] - async fn test_sophia_error_handling() { - // Test error handling scenarios - use crate::errors::SofieError; - - let error = SofieError::ServerStart("Test error".to_string()); - - // Test error display - let display_str = format!("{}", error); - assert!(display_str.contains("Failed to start server")); - assert!(display_str.contains("Test error")); - - // Test error debug - let debug_str = format!("{:?}", error); - assert!(debug_str.contains("ServerStart")); - - // Test error equality - let error2 = SofieError::ServerStart("Test error".to_string()); - assert_eq!(error, error2); - - let error3 = SofieError::ServerStart("Different error".to_string()); - assert_ne!(error, error3); - } - - #[tokio::test] - async fn test_sophia_multiple_instances() { - // Test creating multiple Sofie instances - let sophia_instances: Vec = (0..5) - .map(|_| App::new()) - .collect(); - - // All instances should be valid - assert_eq!(sophia_instances.len(), 5); - - // All should be zero-sized - for sophia in &sophia_instances { - assert_eq!(std::mem::size_of_val(sophia), 0); - } - } -}