diff --git a/examples/kanban/src/main.rs b/examples/kanban/src/main.rs index 470a996..830dc9a 100644 --- a/examples/kanban/src/main.rs +++ b/examples/kanban/src/main.rs @@ -173,7 +173,7 @@ fn registry(state: Arc) -> impl DispatchRegistry { move |form| { // req: examples/001 req: list/003 let mut board = state.board.lock().unwrap(); - let moved = update_card(&mut board, form.value("card_id"), |card| { + let moved = update_card(&mut board, form.parse("card_id"), |card| { card.column = card.column.saturating_sub(1); }); board_effects(&board, if moved { "Card moved left" } else { "Card not found" }) @@ -184,7 +184,7 @@ fn registry(state: Arc) -> impl DispatchRegistry { move |form| { // req: examples/001 req: list/003 let mut board = state.board.lock().unwrap(); - let moved = update_card(&mut board, form.value("card_id"), |card| { + let moved = update_card(&mut board, form.parse("card_id"), |card| { card.column = (card.column + 1).min(COLUMNS.len() - 1); }); board_effects(&board, if moved { "Card moved right" } else { "Card not found" }) @@ -196,7 +196,7 @@ fn registry(state: Arc) -> impl DispatchRegistry { // req: examples/001 req: list/003 let mut board = state.board.lock().unwrap(); let before = board.cards.len(); - if let Some(id) = form.value("card_id").and_then(|id| id.parse::().ok()) { + if let Some(id) = form.parse::("card_id") { board.cards.retain(|card| card.id != id); } board_effects(&board, if board.cards.len() < before { "Card deleted" } else { "Card not found" }) @@ -212,8 +212,12 @@ fn board_effects(board: &BoardState, notice: &'static str) -> impl IntoEffect { ) } -fn update_card(board: &mut BoardState, card_id: Option<&str>, update: impl FnOnce(&mut Card)) -> bool { - let Some(id) = card_id.and_then(|id| id.parse::().ok()) else { +fn update_card( + board: &mut BoardState, + card_id: Option, + update: impl FnOnce(&mut Card), +) -> bool { + let Some(id) = card_id else { return false; }; let Some(card) = board.cards.iter_mut().find(|card| card.id == id) else { diff --git a/examples/techdemo/src/main.rs b/examples/techdemo/src/main.rs index 62ccd06..5ccdc95 100644 --- a/examples/techdemo/src/main.rs +++ b/examples/techdemo/src/main.rs @@ -291,7 +291,7 @@ fn registry(shared: Arc) -> impl DispatchRegistry { let mut demo = shared.demo.lock().unwrap(); let title = form.value("title").unwrap_or("").trim(); let lane = parse_lane(form.value("lane")); - let impact = form.value("impact").and_then(|value| value.parse::().ok()).unwrap_or(5).clamp(1, 9); + let impact = form.parse::("impact").unwrap_or(5).clamp(1, 9); if !title.is_empty() { let id = demo.next_id; demo.next_id += 1; @@ -308,7 +308,7 @@ fn registry(shared: Arc) -> impl DispatchRegistry { move |form| { // req: list/003 req: examples/001 let mut demo = shared.demo.lock().unwrap(); - let title = update_work(&mut demo, form.value("work_id"), |item| { + let title = update_work(&mut demo, form.parse("work_id"), |item| { item.stage = item.stage.advance(); if item.stage == Stage::Shipped { item.lane = LANES.len() - 1; @@ -327,7 +327,7 @@ fn registry(shared: Arc) -> impl DispatchRegistry { // req: list/003 req: examples/001 let mut demo = shared.demo.lock().unwrap(); let lane = parse_lane(form.value("lane")); - let title = update_work(&mut demo, form.value("work_id"), |item| { + let title = update_work(&mut demo, form.parse("work_id"), |item| { item.lane = lane; item.stage = match lane { 0 => Stage::Draft, @@ -336,7 +336,7 @@ fn registry(shared: Arc) -> impl DispatchRegistry { }; }); if let Some(title) = title { - demo.selected_id = form.value("work_id").and_then(|value| value.parse().ok()); + demo.selected_id = form.parse("work_id"); demo.spotlight = format!("{title} moved to {} by drag-and-drop; Rust re-rendered the board slot.", LANES[lane].1); demo.log(format!("Dragged {title} to {}", LANES[lane].1)); } @@ -348,7 +348,7 @@ fn registry(shared: Arc) -> impl DispatchRegistry { move |form| { // req: list/003 req: examples/001 let mut demo = shared.demo.lock().unwrap(); - if let Some(id) = form.value("work_id").and_then(|value| value.parse::().ok()) { + if let Some(id) = form.parse::("work_id") { let before = demo.work.len(); demo.work.retain(|item| item.id != id); if demo.work.len() < before { @@ -367,7 +367,7 @@ fn registry(shared: Arc) -> impl DispatchRegistry { move |form| { // req: examples/001 let mut demo = shared.demo.lock().unwrap(); - if let Some(id) = form.value("work_id").and_then(|value| value.parse::().ok()) { + if let Some(id) = form.parse::("work_id") { if let Some(item) = demo.work.iter().find(|item| item.id == id).cloned() { demo.selected_id = Some(id); demo.spotlight = format!("{} · lane={} · stage={} · impact={}", item.title, LANES[item.lane].1, item.stage.label(), item.impact); diff --git a/slhx-axum/src/lib.rs b/slhx-axum/src/lib.rs index 7b71acc..95cf646 100644 --- a/slhx-axum/src/lib.rs +++ b/slhx-axum/src/lib.rs @@ -243,6 +243,13 @@ impl InteractionForm { .find_map(|(field, value)| (field == name).then_some(value.as_str())) } + pub fn parse(&self, name: &str) -> Option + where + T: std::str::FromStr, + { + self.value(name).and_then(|value| value.parse().ok()) + } + pub fn values<'a>(&'a self, name: &'a str) -> impl Iterator + 'a { self.fields .iter() diff --git a/slhx-axum/tests/response.rs b/slhx-axum/tests/response.rs index 053bd30..9d3b80d 100644 --- a/slhx-axum/tests/response.rs +++ b/slhx-axum/tests/response.rs @@ -117,6 +117,17 @@ fn interaction_form_parses_handle_and_fields() { assert_eq!(form.values("tag").collect::>(), ["a", "b/c"]); } +#[test] +fn interaction_form_parses_typed_values() { + // req: form/004 req: dx/003 + let form = InteractionForm::parse_urlencoded(b"__h=42&count=7&bad=nope") + .expect("form should parse"); + + assert_eq!(form.parse::("count"), Some(7)); + assert_eq!(form.parse::("bad"), None); + assert_eq!(form.parse::("missing"), None); +} + #[test] fn interaction_form_requires_numeric_handle() { assert_eq!(