Skip to main content

hjkl_form/
submit.rs

1//! Submit handler types.
2
3use crate::form::Form;
4
5/// Form submit closure. Consumed via `Option::take` on first call.
6pub type SubmitFn = Box<dyn FnOnce() -> SubmitOutcome + Send>;
7
8/// Outcome returned by [`SubmitFn`]. Surfaced through `FormEvent::Submitted`.
9#[derive(Debug, Clone)]
10pub enum SubmitOutcome {
11    Ok,
12    Err(String),
13}
14
15impl Form {
16    /// Validate every field and, on success, fire the submit closure
17    /// (consumed via `Option::take`). Returns:
18    /// - `Some(SubmitOutcome::Ok)` / `Some(SubmitOutcome::Err(_))` —
19    ///   submit ran (or no submit was registered, in which case `Ok`).
20    /// - `None` — validation failed; per-field errors populated.
21    pub fn try_submit(&mut self) -> Option<SubmitOutcome> {
22        if self.validate_all().is_err() {
23            self.bump_dirty();
24            return None;
25        }
26        Some(match self.submit.take() {
27            Some(submit) => submit(),
28            None => SubmitOutcome::Ok,
29        })
30    }
31}
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36    use crate::field::{Field, FieldMeta, SubmitField, TextFieldEditor};
37    use std::sync::Arc;
38    use std::sync::atomic::{AtomicUsize, Ordering};
39
40    #[test]
41    fn successful_submit_consumes_fn() {
42        let count = Arc::new(AtomicUsize::new(0));
43        let c2 = count.clone();
44        let mut form = Form::new()
45            .with_field(Field::Submit(SubmitField::new(FieldMeta::new("Sub"))))
46            .with_submit(Box::new(move || {
47                c2.fetch_add(1, Ordering::SeqCst);
48                SubmitOutcome::Ok
49            }));
50        let r = form.try_submit();
51        assert!(matches!(r, Some(SubmitOutcome::Ok)));
52        assert_eq!(count.load(Ordering::SeqCst), 1);
53        // Second call: closure already consumed, but validation still
54        // passes (no validators), so we get a None-fallback Ok.
55        let r2 = form.try_submit();
56        assert!(matches!(r2, Some(SubmitOutcome::Ok)));
57        assert_eq!(count.load(Ordering::SeqCst), 1);
58    }
59
60    #[test]
61    fn try_submit_blocked_by_validator() {
62        let count = Arc::new(AtomicUsize::new(0));
63        let c2 = count.clone();
64        let mut name = TextFieldEditor::with_meta(FieldMeta::new("Name").required(true), 1);
65        name.validator = Some(Box::new(|s: &str| {
66            if s.is_empty() {
67                Err("required".into())
68            } else {
69                Ok(())
70            }
71        }));
72        let mut form = Form::new()
73            .with_field(Field::SingleLineText(name))
74            .with_field(Field::Submit(SubmitField::new(FieldMeta::new("Sub"))))
75            .with_submit(Box::new(move || {
76                c2.fetch_add(1, Ordering::SeqCst);
77                SubmitOutcome::Ok
78            }));
79        let r = form.try_submit();
80        assert!(r.is_none());
81        assert_eq!(count.load(Ordering::SeqCst), 0);
82    }
83}