feat(axum): parse typed interaction values
Add InteractionForm::parse for common typed field extraction and use it in example interaction handlers to avoid repeated string parsing plumbing. req: form/004 req: dx/003 req: examples/003
This commit is contained in:
@@ -173,7 +173,7 @@ fn registry(state: Arc<AppState>) -> 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<AppState>) -> 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<AppState>) -> 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::<u64>().ok()) {
|
||||
if let Some(id) = form.parse::<u64>("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::<u64>().ok()) else {
|
||||
fn update_card(
|
||||
board: &mut BoardState,
|
||||
card_id: Option<u64>,
|
||||
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 {
|
||||
|
||||
@@ -291,7 +291,7 @@ fn registry(shared: Arc<Shared>) -> 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::<u8>().ok()).unwrap_or(5).clamp(1, 9);
|
||||
let impact = form.parse::<u8>("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<Shared>) -> 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<Shared>) -> 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<Shared>) -> 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<Shared>) -> 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::<u64>().ok()) {
|
||||
if let Some(id) = form.parse::<u64>("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<Shared>) -> 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::<u64>().ok()) {
|
||||
if let Some(id) = form.parse::<u64>("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);
|
||||
|
||||
@@ -243,6 +243,13 @@ impl InteractionForm {
|
||||
.find_map(|(field, value)| (field == name).then_some(value.as_str()))
|
||||
}
|
||||
|
||||
pub fn parse<T>(&self, name: &str) -> Option<T>
|
||||
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<Item = &'a str> + 'a {
|
||||
self.fields
|
||||
.iter()
|
||||
|
||||
@@ -117,6 +117,17 @@ fn interaction_form_parses_handle_and_fields() {
|
||||
assert_eq!(form.values("tag").collect::<Vec<_>>(), ["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::<u32>("count"), Some(7));
|
||||
assert_eq!(form.parse::<u32>("bad"), None);
|
||||
assert_eq!(form.parse::<u32>("missing"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interaction_form_requires_numeric_handle() {
|
||||
assert_eq!(
|
||||
|
||||
Reference in New Issue
Block a user