diff --git a/examples/kanban/src/main.rs b/examples/kanban/src/main.rs index 331a913..1e3dfa0 100644 --- a/examples/kanban/src/main.rs +++ b/examples/kanban/src/main.rs @@ -146,7 +146,7 @@ async fn events(Query(params): Query>) -> impl IntoResp fn registry(state: Arc) -> HandlerRegistry { HandlerRegistry::new(ui::BUILD_FINGERPRINT) - .register(ui::board::handles::create_card.id().id, { + .register_handle(ui::board::handles::create_card, { let state = state.clone(); move |form| { // req: examples/001 req: form/002 @@ -161,7 +161,7 @@ fn registry(state: Arc) -> HandlerRegistry { board_effects(&board, "Card added") } }) - .register(ui::board::handles::move_left.id().id, { + .register_handle(ui::board::handles::move_left, { let state = state.clone(); move |form| { // req: examples/001 req: list/003 @@ -172,7 +172,7 @@ fn registry(state: Arc) -> HandlerRegistry { board_effects(&board, if moved { "Card moved left" } else { "Card not found" }) } }) - .register(ui::board::handles::move_right.id().id, { + .register_handle(ui::board::handles::move_right, { let state = state.clone(); move |form| { // req: examples/001 req: list/003 @@ -183,7 +183,7 @@ fn registry(state: Arc) -> HandlerRegistry { board_effects(&board, if moved { "Card moved right" } else { "Card not found" }) } }) - .register(ui::board::handles::delete_card.id().id, { + .register_handle(ui::board::handles::delete_card, { let state = state.clone(); move |form| { // req: examples/001 req: list/003 diff --git a/examples/techdemo/src/main.rs b/examples/techdemo/src/main.rs index 486797a..cb67bdd 100644 --- a/examples/techdemo/src/main.rs +++ b/examples/techdemo/src/main.rs @@ -278,7 +278,7 @@ async fn events(Query(params): Query>) -> impl IntoResp fn registry(shared: Arc) -> HandlerRegistry { HandlerRegistry::new(ui::BUILD_FINGERPRINT) - .register(ui::control_center::handles::launch_work.id().id, { + .register_handle(ui::control_center::handles::launch_work, { let shared = shared.clone(); move |form| { // req: form/002 req: examples/001 @@ -297,7 +297,7 @@ fn registry(shared: Arc) -> HandlerRegistry { demo_effects(&demo, "Launch accepted ยท 4 targets updated") } }) - .register(ui::control_center::handles::advance_work.id().id, { + .register_handle(ui::control_center::handles::advance_work, { let shared = shared.clone(); move |form| { // req: list/003 req: examples/001 @@ -315,7 +315,7 @@ fn registry(shared: Arc) -> HandlerRegistry { demo_effects(&demo, "Pipeline advanced") } }) - .register(ui::control_center::handles::move_to_lane.id().id, { + .register_handle(ui::control_center::handles::move_to_lane, { let shared = shared.clone(); move |form| { // req: list/003 req: examples/001 @@ -337,7 +337,7 @@ fn registry(shared: Arc) -> HandlerRegistry { demo_effects(&demo, "Drag-and-drop move persisted") } }) - .register(ui::control_center::handles::delete_work.id().id, { + .register_handle(ui::control_center::handles::delete_work, { let shared = shared.clone(); move |form| { // req: list/003 req: examples/001 @@ -356,7 +356,7 @@ fn registry(shared: Arc) -> HandlerRegistry { demo_effects(&demo, "Card removed") } }) - .register(ui::control_center::handles::spotlight_work.id().id, { + .register_handle(ui::control_center::handles::spotlight_work, { let shared = shared.clone(); move |form| { // req: examples/001 @@ -371,7 +371,7 @@ fn registry(shared: Arc) -> HandlerRegistry { demo_effects(&demo, "Inspector focused") } }) - .register(ui::control_center::handles::simulate_push.id().id, { + .register_handle(ui::control_center::handles::simulate_push, { let shared = shared.clone(); move |_| { // req: push/003 req: examples/001 @@ -384,7 +384,7 @@ fn registry(shared: Arc) -> HandlerRegistry { ) } }) - .register(ui::control_center::handles::reset_demo.id().id, { + .register_handle(ui::control_center::handles::reset_demo, { let shared = shared.clone(); move |_| { // req: examples/001 diff --git a/examples/v0/src/main.rs b/examples/v0/src/main.rs index 95f6d6d..43b28f8 100644 --- a/examples/v0/src/main.rs +++ b/examples/v0/src/main.rs @@ -116,7 +116,7 @@ async fn events(Query(params): Query>) -> impl IntoResp fn registry(state: Arc) -> HandlerRegistry { HandlerRegistry::new(ui::BUILD_FINGERPRINT) - .register(ui::counter::handles::increment.id().id, { + .register_handle(ui::counter::handles::increment, { let state = state.clone(); move |_| { // req: examples/001 @@ -125,7 +125,7 @@ fn registry(state: Arc) -> HandlerRegistry { ui::counter::slots::counter_value.text(*counter) } }) - .register(ui::todos::handles::add_todo.id().id, { + .register_handle(ui::todos::handles::add_todo, { let state = state.clone(); move |form| { // req: examples/001 @@ -141,7 +141,7 @@ fn registry(state: Arc) -> HandlerRegistry { ) } }) - .register(ui::wizard::handles::next_step.id().id, { + .register_handle(ui::wizard::handles::next_step, { let state = state.clone(); move |_| { // req: examples/001 @@ -150,7 +150,7 @@ fn registry(state: Arc) -> HandlerRegistry { ui::wizard::slots::wizard_step.text(format!("Step {}", *step + 1)) } }) - .register(ui::auth::handles::login.id().id, |form| { + .register_handle(ui::auth::handles::login, |form| { // req: examples/001 let ok = form.value("email") == Some("demo@example.com") && form.value("password").is_some_and(|password| !password.is_empty()); @@ -160,7 +160,7 @@ fn registry(state: Arc) -> HandlerRegistry { "Try demo@example.com with any password" }) }) - .register(ui::page_swap::handles::load_docs.id().id, |_| { + .register_handle(ui::page_swap::handles::load_docs, |_| { // req: page_swap/002, req: examples/001 ( ui::page_swap::slots::content.html(render_docs_content( diff --git a/slhx-axum/src/lib.rs b/slhx-axum/src/lib.rs index a62297c..b36e9bb 100644 --- a/slhx-axum/src/lib.rs +++ b/slhx-axum/src/lib.rs @@ -5,7 +5,7 @@ use axum::http::{header, request::Parts, HeaderMap, HeaderValue, Request, Respon use axum::response::sse::{Event, Sse}; use axum::response::IntoResponse; use futures_util::{Stream, StreamExt}; -use slhx_core::{BuildFingerprint, EffectBatch, IntoEffect}; +use slhx_core::{BuildFingerprint, EffectBatch, Handle, IntoEffect}; use std::collections::BTreeMap; use std::convert::Infallible; @@ -262,6 +262,17 @@ impl HandlerRegistry { self } + pub fn register_handle( + self, + handle: Handle, + handler: impl Fn(InteractionForm) -> E + Send + Sync + 'static, + ) -> Self + where + E: IntoEffect, + { + self.register(handle.id().id, handler) + } + pub fn dispatch(&self, form: InteractionForm) -> Result { let handle_id = form.handle_id; let Some(handler) = self.handlers.get(&handle_id) else { diff --git a/slhx-axum/tests/response.rs b/slhx-axum/tests/response.rs index ee7fbe1..7252230 100644 --- a/slhx-axum/tests/response.rs +++ b/slhx-axum/tests/response.rs @@ -6,7 +6,7 @@ use slhx_axum::{ InteractionFormRejection, PageMode, PageRequest, PageResponse, SLHX_CONTENT_TYPE, SLHX_FINGERPRINT_HEADER, SLHX_PARTIAL_HEADER, SLHX_RUNTIME_CONTENT_TYPE, SLHX_TITLE_HEADER, }; -use slhx_core::{push, BuildFingerprint, Slot}; +use slhx_core::{push, BuildFingerprint, Handle, Slot}; fn selector(value: &str) -> Selector { Selector::parse(value).expect("test selector parses") @@ -100,14 +100,16 @@ fn interaction_form_preserves_hidden_csrf_fields_for_extractors() { } #[test] -fn handler_registry_dispatches_by_numeric_handle_id() { +fn handler_registry_dispatches_by_checked_handle() { + // req: ceremony/004 req: public_api/001 let title = Slot::::new(7); - let registry = HandlerRegistry::new(BuildFingerprint(123)).register(42, move |form| { + let create = Handle::<()>::new(42); + let registry = HandlerRegistry::new(BuildFingerprint(123)).register_handle(create, move |form| { title.text(form.value("title").unwrap_or("")) }); let response = registry - .dispatch(InteractionForm::new(42, [("title".into(), "Hello".into())])) + .dispatch(InteractionForm::new(create.id().id, [("title".into(), "Hello".into())])) .unwrap(); assert_eq!(response.batch.fingerprint, BuildFingerprint(123));