reinhardt_forms/
wizard.rs1use crate::form::{Form, FormError};
2use std::collections::HashMap;
3
4type WizardSessionData = HashMap<String, HashMap<String, serde_json::Value>>;
6
7type WizardConditionFn = Box<dyn Fn(&WizardSessionData) -> bool + Send + Sync>;
9
10pub struct FormWizard {
12 steps: Vec<WizardStep>,
13 current_step: usize,
14 session_data: WizardSessionData,
15}
16
17pub struct WizardStep {
19 pub name: String,
20 pub form: Form,
21 pub condition: Option<WizardConditionFn>,
22}
23
24impl WizardStep {
25 pub fn new(name: String, form: Form) -> Self {
37 Self {
38 name,
39 form,
40 condition: None,
41 }
42 }
43 pub fn with_condition<F>(mut self, condition: F) -> Self
61 where
62 F: Fn(&WizardSessionData) -> bool + Send + Sync + 'static,
63 {
64 self.condition = Some(Box::new(condition));
65 self
66 }
67 pub fn is_available(&self, session_data: &WizardSessionData) -> bool {
68 if let Some(condition) = &self.condition {
69 condition(session_data)
70 } else {
71 true
72 }
73 }
74}
75
76impl FormWizard {
77 pub fn new(_prefix: String) -> Self {
89 Self {
90 steps: vec![],
91 current_step: 0,
92 session_data: HashMap::new(),
93 }
94 }
95
96 pub fn steps(&self) -> &Vec<WizardStep> {
97 &self.steps
98 }
99 pub fn add_step(&mut self, step: WizardStep) {
113 self.steps.push(step);
114 }
115 pub fn current_step(&self) -> usize {
116 self.current_step
117 }
118 pub fn current_step_name(&self) -> Option<&str> {
119 self.steps.get(self.current_step).map(|s| s.name.as_str())
120 }
121 pub fn current_form(&self) -> Option<&Form> {
122 self.steps.get(self.current_step).map(|s| &s.form)
123 }
124 pub fn current_form_mut(&mut self) -> Option<&mut Form> {
125 self.steps.get_mut(self.current_step).map(|s| &mut s.form)
126 }
127 pub fn total_steps(&self) -> usize {
128 self.steps.len()
129 }
130 pub fn is_first_step(&self) -> bool {
131 self.current_step == 0
132 }
133 pub fn is_last_step(&self) -> bool {
134 self.current_step + 1 >= self.steps.len()
135 }
136 pub fn next_step(&mut self) -> Result<(), String> {
154 if self.is_last_step() {
155 return Err("Already at last step".to_string());
156 }
157
158 for i in (self.current_step + 1)..self.steps.len() {
160 if self.steps[i].is_available(&self.session_data) {
161 self.current_step = i;
162 return Ok(());
163 }
164 }
165
166 Err("No available next step".to_string())
167 }
168 pub fn previous_step(&mut self) -> Result<(), String> {
187 if self.is_first_step() {
188 return Err("Already at first step".to_string());
189 }
190
191 for i in (0..self.current_step).rev() {
193 if self.steps[i].is_available(&self.session_data) {
194 self.current_step = i;
195 return Ok(());
196 }
197 }
198
199 Err("No available previous step".to_string())
200 }
201 pub fn goto_step(&mut self, name: &str) -> Result<(), String> {
240 let target_index = self
242 .steps
243 .iter()
244 .position(|step| step.name == name && step.is_available(&self.session_data))
245 .ok_or_else(|| format!("Step '{}' not found or not available", name))?;
246
247 if target_index <= self.current_step {
249 self.current_step = target_index;
250 return Ok(());
251 }
252
253 for i in self.current_step..target_index {
256 let step_name = &self.steps[i].name;
257 if !self.session_data.contains_key(step_name) {
258 return Err(format!(
259 "Cannot skip to step '{}': step '{}' has not been completed",
260 name, step_name
261 ));
262 }
263 }
264
265 self.current_step = target_index;
266 Ok(())
267 }
268 pub fn save_step_data(
288 &mut self,
289 data: HashMap<String, serde_json::Value>,
290 ) -> Result<(), FormError> {
291 if let Some(step) = self.steps.get(self.current_step) {
292 self.session_data.insert(step.name.clone(), data);
293 Ok(())
294 } else {
295 Err(FormError::Validation("Invalid step".to_string()))
296 }
297 }
298 pub fn get_all_data(&self) -> &HashMap<String, HashMap<String, serde_json::Value>> {
299 &self.session_data
300 }
301 pub fn get_step_data(&self, step_name: &str) -> Option<&HashMap<String, serde_json::Value>> {
302 self.session_data.get(step_name)
303 }
304 pub fn clear_data(&mut self) {
305 self.session_data.clear();
306 self.current_step = 0;
307 }
308 pub fn process_step(
325 &mut self,
326 data: HashMap<String, serde_json::Value>,
327 ) -> Result<bool, FormError> {
328 if let Some(form) = self.current_form_mut() {
329 form.bind(data.clone());
330
331 if form.is_valid() {
332 self.save_step_data(data)?;
333
334 if !self.is_last_step() {
335 self.next_step().map_err(FormError::Validation)?;
336 Ok(false) } else {
338 Ok(true) }
340 } else {
341 Err(FormError::Validation("Form validation failed".to_string()))
342 }
343 } else {
344 Err(FormError::Validation("Invalid step".to_string()))
345 }
346 }
347 pub fn progress_percentage(&self) -> f32 {
348 if self.steps.is_empty() {
349 return 0.0;
350 }
351 ((self.current_step + 1) as f32 / self.steps.len() as f32) * 100.0
352 }
353}
354
355#[cfg(test)]
356mod tests {
357 use super::*;
358 use crate::fields::CharField;
359
360 #[test]
361 fn test_wizard_basic() {
362 let mut wizard = FormWizard::new("registration".to_string());
363
364 let mut form1 = Form::new();
365 form1.add_field(Box::new(CharField::new("username".to_string())));
366 wizard.add_step(WizardStep::new("account".to_string(), form1));
367
368 let mut form2 = Form::new();
369 form2.add_field(Box::new(CharField::new("email".to_string())));
370 wizard.add_step(WizardStep::new("contact".to_string(), form2));
371
372 assert_eq!(wizard.total_steps(), 2);
373 assert_eq!(wizard.current_step(), 0);
374 assert_eq!(wizard.current_step_name(), Some("account"));
375 assert!(wizard.is_first_step());
376 assert!(!wizard.is_last_step());
377 }
378
379 #[test]
380 fn test_wizard_navigation() {
381 let mut wizard = FormWizard::new("test".to_string());
382
383 for i in 1..=3 {
384 let mut form = Form::new();
385 form.add_field(Box::new(CharField::new(format!("field{}", i))));
386 wizard.add_step(WizardStep::new(format!("step{}", i), form));
387 }
388
389 assert_eq!(wizard.current_step(), 0);
390
391 wizard.next_step().unwrap();
392 assert_eq!(wizard.current_step(), 1);
393
394 wizard.next_step().unwrap();
395 assert_eq!(wizard.current_step(), 2);
396 assert!(wizard.is_last_step());
397
398 wizard.previous_step().unwrap();
399 assert_eq!(wizard.current_step(), 1);
400 }
401
402 #[test]
403 fn test_wizard_conditional_step() {
404 let mut wizard = FormWizard::new("test".to_string());
405
406 let mut form1 = Form::new();
407 form1.add_field(Box::new(CharField::new("type".to_string())));
408 wizard.add_step(WizardStep::new("type_selection".to_string(), form1));
409
410 let mut form2 = Form::new();
411 form2.add_field(Box::new(CharField::new("premium_field".to_string())));
412 let step2 = WizardStep::new("premium".to_string(), form2).with_condition(|data| {
413 data.get("type_selection")
414 .and_then(|d| d.get("type"))
415 .and_then(|v| v.as_str())
416 .map(|s| s == "premium")
417 .unwrap_or(false)
418 });
419 wizard.add_step(step2);
420
421 assert!(!wizard.steps[1].is_available(&wizard.session_data));
423
424 let mut data = HashMap::new();
426 data.insert("type".to_string(), serde_json::json!("premium"));
427 wizard.save_step_data(data).unwrap();
428
429 assert!(wizard.steps[1].is_available(&wizard.session_data));
430 }
431
432 #[test]
433 fn test_wizard_progress() {
434 let mut wizard = FormWizard::new("test".to_string());
435
436 for i in 1..=4 {
437 let mut form = Form::new();
438 form.add_field(Box::new(CharField::new(format!("field{}", i))));
439 wizard.add_step(WizardStep::new(format!("step{}", i), form));
440 }
441
442 assert_eq!(wizard.progress_percentage(), 25.0); wizard.next_step().unwrap();
445 assert_eq!(wizard.progress_percentage(), 50.0); wizard.next_step().unwrap();
448 assert_eq!(wizard.progress_percentage(), 75.0); wizard.next_step().unwrap();
451 assert_eq!(wizard.progress_percentage(), 100.0); }
453
454 #[test]
455 fn test_wizard_goto_step_backward_always_allowed() {
456 let mut wizard = FormWizard::new("test".to_string());
457
458 for i in 1..=3 {
459 let mut form = Form::new();
460 form.add_field(Box::new(CharField::new(format!("field{}", i))));
461 wizard.add_step(WizardStep::new(format!("step{}", i), form));
462 }
463
464 let mut data = HashMap::new();
466 data.insert("field1".to_string(), serde_json::json!("value"));
467 wizard.save_step_data(data.clone()).unwrap();
468 wizard.next_step().unwrap();
469 data.clear();
470 data.insert("field2".to_string(), serde_json::json!("value"));
471 wizard.save_step_data(data).unwrap();
472 wizard.next_step().unwrap();
473 assert_eq!(wizard.current_step(), 2);
474
475 wizard.goto_step("step1").unwrap();
477 assert_eq!(wizard.current_step(), 0);
478 assert_eq!(wizard.current_step_name(), Some("step1"));
479 }
480
481 #[test]
482 fn test_wizard_goto_step_forward_requires_completed_steps() {
483 let mut wizard = FormWizard::new("test".to_string());
484
485 for i in 1..=3 {
486 let mut form = Form::new();
487 form.add_field(Box::new(CharField::new(format!("field{}", i))));
488 wizard.add_step(WizardStep::new(format!("step{}", i), form));
489 }
490
491 let result = wizard.goto_step("step3");
493 assert!(result.is_err());
494 assert_eq!(wizard.current_step(), 0);
495 }
496
497 #[test]
498 fn test_wizard_goto_step_forward_after_completing_steps() {
499 let mut wizard = FormWizard::new("test".to_string());
500
501 for i in 1..=3 {
502 let mut form = Form::new();
503 form.add_field(Box::new(CharField::new(format!("field{}", i))));
504 wizard.add_step(WizardStep::new(format!("step{}", i), form));
505 }
506
507 let mut data = HashMap::new();
509 data.insert("field1".to_string(), serde_json::json!("value1"));
510 wizard.save_step_data(data).unwrap();
511
512 wizard.next_step().unwrap();
514 let mut data2 = HashMap::new();
515 data2.insert("field2".to_string(), serde_json::json!("value2"));
516 wizard.save_step_data(data2).unwrap();
517
518 wizard.goto_step("step3").unwrap();
520 assert_eq!(wizard.current_step(), 2);
521 assert_eq!(wizard.current_step_name(), Some("step3"));
522 }
523}