diff --git a/Cargo.toml b/Cargo.toml index a3c094f1..7eb11688 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] +resolver = "2" members = [ "viz", "viz-core", @@ -30,8 +31,8 @@ members = [ "examples/tracing", "examples/graceful-shutdown", "examples/databases/*", + "examples/htmlx", ] -resolver = "2" [workspace.package] version = "0.5.1" diff --git a/examples/README.md b/examples/README.md index 0382ddb3..39130b1c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -31,6 +31,7 @@ Here you can find a lot of small crabs 🦀. * [maud](templates/maud) * [minijinja](templates/minijinja) * [Tracing aka logging](tracing) +* [htmlx](htmlx) ## Usage diff --git a/examples/databases/sea-orm/Cargo.toml b/examples/databases/sea-orm/Cargo.toml index 2f4199b7..747f675a 100644 --- a/examples/databases/sea-orm/Cargo.toml +++ b/examples/databases/sea-orm/Cargo.toml @@ -8,7 +8,7 @@ publish = false viz = { workspace = true, features = ["serve"] } serde.workspace = true -tokio = { workspace = true, features = [ "rt-multi-thread", "macros" ] } +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } sea-orm = { version = "0.12", features = ["runtime-tokio-rustls", "sqlx-sqlite"] } [lints] diff --git a/examples/forms/form/Cargo.toml b/examples/forms/form/Cargo.toml index cdb88888..14589d2f 100644 --- a/examples/forms/form/Cargo.toml +++ b/examples/forms/form/Cargo.toml @@ -8,7 +8,4 @@ publish = false viz.workspace = true serde = { workspace = true, features = ["derive"] } -tokio = { workspace = true, features = [ - "rt-multi-thread", - "macros", -] } +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } diff --git a/examples/graceful-shutdown/Cargo.toml b/examples/graceful-shutdown/Cargo.toml index e76f6e0b..a61f2df9 100644 --- a/examples/graceful-shutdown/Cargo.toml +++ b/examples/graceful-shutdown/Cargo.toml @@ -7,4 +7,4 @@ publish = false [dependencies] viz.workspace = true -tokio = { workspace = true, features = [ "rt-multi-thread", "macros", "time" ] } +tokio = { workspace = true, features = ["rt-multi-thread", "macros", "time"] } diff --git a/examples/hello-world/Cargo.toml b/examples/hello-world/Cargo.toml index 01368560..83109cdc 100644 --- a/examples/hello-world/Cargo.toml +++ b/examples/hello-world/Cargo.toml @@ -7,4 +7,4 @@ publish = false [dependencies] viz.workspace = true -tokio = { workspace = true, features = [ "rt-multi-thread", "macros" ] } +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } diff --git a/examples/htmlx/Cargo.toml b/examples/htmlx/Cargo.toml new file mode 100644 index 00000000..6c9786fc --- /dev/null +++ b/examples/htmlx/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "htmlx" +version = "0.1.0" +edition.workspace = true +publish = false + +[dependencies] +viz = { workspace = true, features = ["serve"] } + +serde = { workspace = true, features = ["derive"] } +serde_json.workspace = true +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } + +handlebars = { version = "4.5", features = ["dir_source"] } +once_cell = "1.19" diff --git a/examples/htmlx/src/main.rs b/examples/htmlx/src/main.rs new file mode 100644 index 00000000..315f8dbe --- /dev/null +++ b/examples/htmlx/src/main.rs @@ -0,0 +1,111 @@ +// #![deny(warnings)] + +use handlebars::Handlebars; +use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use std::{ + net::SocketAddr, + sync::{Arc, Mutex, PoisonError}, +}; +use tokio::net::TcpListener; +use viz::{ + header::HeaderValue, middleware::limits, serve, types::State, Error, IntoResponse, Request, + RequestExt, Response, ResponseExt, Result, Router, StatusCode, Tree, +}; + +/// In-memory todo store +type DB = Arc>>; + +#[derive(Debug, Clone, Deserialize, Serialize)] +struct Todo { + pub text: String, + pub completed: bool, +} + +static TPLS: Lazy = Lazy::new(|| { + let mut h = Handlebars::new(); + h.register_templates_directory(".html", "examples/htmlx/templates") + .unwrap(); + h +}); + +#[allow(clippy::needless_pass_by_value)] +fn into_error(e: PoisonError) -> Error { + e.to_string().into_error() +} + +async fn index(req: Request) -> Result { + let todos = req + .state::() + .unwrap() + .lock() + .map_err(into_error)? + .clone(); + let body = TPLS + .render( + "index", + &json!({ + "todos": todos + }), + ) + .map_err(Error::normal)?; + Ok(Response::html(body)) +} + +async fn list(req: Request) -> Result { + let todos = req + .state::() + .unwrap() + .lock() + .map_err(into_error)? + .clone(); + let body = TPLS + .render( + "todos", + &json!({ + "todos": todos + }), + ) + .map_err(Error::normal)?; + Ok(Response::html(body)) +} + +async fn create(mut req: Request) -> Result { + let todo = req.form::().await?; + let db = req.state::().unwrap(); + + let mut todos = db.lock().map_err(into_error)?; + todos.push(todo); + + let mut resp = StatusCode::CREATED.into_response(); + resp.headers_mut() + .insert("HX-Trigger", HeaderValue::from_static("newTodo")); + Ok(resp) +} + +#[tokio::main] +async fn main() -> Result<()> { + let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + let listener = TcpListener::bind(addr).await?; + println!("listening on http://{addr}"); + + let app = Router::new() + .get("/", index) + .get("/todos", list) + .post("/todos", create) + .any("/*", |_| async { Ok(Response::text("Welcome!")) }) + .with(State::new(DB::default())) + .with(limits::Config::default()); + let tree = Arc::new(Tree::from(app)); + + loop { + let (stream, addr) = listener.accept().await?; + let tree = tree.clone(); + tokio::task::spawn(async move { + if let Err(err) = serve(stream, tree, Some(addr)).await { + eprintln!("Error while serving HTTP connection: {err}"); + } + }); + } +} diff --git a/examples/htmlx/templates/index.html b/examples/htmlx/templates/index.html new file mode 100644 index 00000000..43c005e6 --- /dev/null +++ b/examples/htmlx/templates/index.html @@ -0,0 +1,30 @@ + + + + Viz & htmlx + + + +
+ + + + +
+
+
    + {{#each todos as | todo |}} +
  • {{text}}
  • + {{/each}} +
+
+ + diff --git a/examples/htmlx/templates/todos.html b/examples/htmlx/templates/todos.html new file mode 100644 index 00000000..4fc12737 --- /dev/null +++ b/examples/htmlx/templates/todos.html @@ -0,0 +1,5 @@ +
    +{{#each todos as | todo |}} +
  • {{text}} {{complated}}
  • +{{/each}} +
diff --git a/examples/limits/Cargo.toml b/examples/limits/Cargo.toml index c9345e38..c9567055 100644 --- a/examples/limits/Cargo.toml +++ b/examples/limits/Cargo.toml @@ -7,8 +7,5 @@ publish = false [dependencies] viz = { workspace = true, features = ["limits", "json", "form", "multipart"] } -serde = {version = "1.0", features = ["derive"] } -tokio = { workspace = true, features = [ - "rt-multi-thread", - "macros", -] } +serde = { version = "1.0", features = ["derive"] } +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } diff --git a/examples/otel/metrics/Cargo.toml b/examples/otel/metrics/Cargo.toml index 853e4632..30123c37 100644 --- a/examples/otel/metrics/Cargo.toml +++ b/examples/otel/metrics/Cargo.toml @@ -7,6 +7,6 @@ publish = false [dependencies] viz = { workspace = true, features = ["otel-metrics", "otel-prometheus"] } -tokio = { workspace = true, features = [ "rt-multi-thread", "macros" ] } +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } opentelemetry = { workspace = true, features = ["metrics"]} opentelemetry_sdk = { workspace = true, features = ["metrics"] } diff --git a/examples/otel/tracing/Cargo.toml b/examples/otel/tracing/Cargo.toml index e70e7dde..b7686c51 100644 --- a/examples/otel/tracing/Cargo.toml +++ b/examples/otel/tracing/Cargo.toml @@ -7,7 +7,7 @@ publish = false [dependencies] viz = { workspace = true, features = ["otel-tracing"] } -tokio = { workspace = true, features = [ "rt-multi-thread", "macros" ] } +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } opentelemetry.workspace = true opentelemetry_sdk = { workspace = true, features = ["trace", "rt-tokio-current-thread"] } opentelemetry-jaeger = { version = "0.20", features = ["rt-tokio-current-thread"]} diff --git a/examples/templates/README.md b/examples/templates/README.md index 7c93879a..f514642d 100644 --- a/examples/templates/README.md +++ b/examples/templates/README.md @@ -5,6 +5,7 @@ ## Examples * [askama](askama) +* [handlebars](../htmlx) * [markup](markup) * [maud](maud) * [minijinja](minijinja) diff --git a/examples/templates/askama/Cargo.toml b/examples/templates/askama/Cargo.toml index c7e1771f..5d153ae9 100644 --- a/examples/templates/askama/Cargo.toml +++ b/examples/templates/askama/Cargo.toml @@ -7,5 +7,5 @@ publish = false [dependencies] viz.workspace = true -tokio = { workspace = true, features = [ "rt-multi-thread", "macros" ] } +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } askama = "0.12" diff --git a/examples/templates/markup/Cargo.toml b/examples/templates/markup/Cargo.toml index 58245d05..836ffe6d 100644 --- a/examples/templates/markup/Cargo.toml +++ b/examples/templates/markup/Cargo.toml @@ -7,7 +7,7 @@ publish = false [dependencies] viz.workspace = true -tokio = { workspace = true, features = [ "rt-multi-thread", "macros" ] } +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } markup = "0.15" v_htmlescape = "0.15" diff --git a/examples/templates/maud/Cargo.toml b/examples/templates/maud/Cargo.toml index bcc2847e..8e39acb3 100644 --- a/examples/templates/maud/Cargo.toml +++ b/examples/templates/maud/Cargo.toml @@ -7,5 +7,5 @@ publish = false [dependencies] viz.workspace = true -tokio = { workspace = true, features = [ "rt-multi-thread", "macros" ] } +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } maud = "0.25" diff --git a/examples/templates/minijinja/Cargo.toml b/examples/templates/minijinja/Cargo.toml index 7e9d07ef..a68e658e 100644 --- a/examples/templates/minijinja/Cargo.toml +++ b/examples/templates/minijinja/Cargo.toml @@ -8,6 +8,6 @@ publish = false viz.workspace = true serde.workspace = true -tokio = { workspace = true, features = [ "rt-multi-thread", "macros" ] } +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } minijinja = { version = "1", features = ["loader"] } once_cell = "1.19" diff --git a/examples/templates/minijinja/src/main.rs b/examples/templates/minijinja/src/main.rs index df72c376..46fad820 100644 --- a/examples/templates/minijinja/src/main.rs +++ b/examples/templates/minijinja/src/main.rs @@ -9,7 +9,7 @@ use serde::Serialize; use tokio::net::TcpListener; use viz::{serve, BytesMut, Error, Request, Response, ResponseExt, Result, Router, Tree}; -static MINIJINJA: Lazy = Lazy::new(|| { +static TPLS: Lazy = Lazy::new(|| { let mut env = Environment::new(); env.set_loader(path_loader("examples/templates/minijinja/templates")); env @@ -24,8 +24,7 @@ struct User<'a> { async fn index(_: Request) -> Result { let mut buf = BytesMut::with_capacity(512); buf.extend( - MINIJINJA - .get_template("index.html") + TPLS.get_template("index.html") .map_err(Error::normal)? .render(context! { title => "Viz.rs", diff --git a/examples/templates/tera/Cargo.toml b/examples/templates/tera/Cargo.toml index 13b740a7..d94d18d2 100644 --- a/examples/templates/tera/Cargo.toml +++ b/examples/templates/tera/Cargo.toml @@ -8,6 +8,6 @@ publish = false viz.workspace = true serde.workspace = true -tokio = { workspace = true, features = [ "rt-multi-thread", "macros" ] } +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } tera = "1.18" once_cell = "1.19" diff --git a/examples/templates/tera/src/main.rs b/examples/templates/tera/src/main.rs index 47b49b0f..1ec3438e 100644 --- a/examples/templates/tera/src/main.rs +++ b/examples/templates/tera/src/main.rs @@ -9,7 +9,7 @@ use tera::{Context, Tera}; use tokio::net::TcpListener; use viz::{serve, BytesMut, Error, Request, Response, ResponseExt, Result, Router, Tree}; -static TERA: Lazy = +static TPLS: Lazy = Lazy::new(|| Tera::new("examples/templates/tera/templates/**/*").unwrap()); #[derive(Serialize)] @@ -36,7 +36,7 @@ async fn index(_: Request) -> Result { ); let mut buf = BytesMut::with_capacity(512); buf.extend( - TERA.render("index.html", &ctx) + TPLS.render("index.html", &ctx) .map_err(Error::normal)? .as_bytes(), );