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,
21 pub form: Form,
23 pub condition: Option<WizardConditionFn>,
25}
26
27impl WizardStep {
28 pub fn new(name: String, form: Form) -> Self {
40 Self {
41 name,
42 form,
43 condition: None,
44 }
45 }
46 pub fn with_condition<F>(mut self, condition: F) -> Self
64 where
65 F: Fn(&WizardSessionData) -> bool + Send + Sync + 'static,
66 {
67 self.condition = Some(Box::new(condition));
68 self
69 }
70 pub fn is_available(&self, session_data: &WizardSessionData) -> bool {
72 if let Some(condition) = &self.condition {
73 condition(session_data)
74 } else {
75 true
76 }
77 }
78}
79
80impl FormWizard {
81 pub fn new(_prefix: String) -> Self {
93 Self {
94 steps: vec![],
95 current_step: 0,
96 session_data: HashMap::new(),
97 }
98 }
99
100 pub fn steps(&self) -> &Vec<WizardStep> {
102 &self.steps
103 }
104 pub fn add_step(&mut self, step: WizardStep) {
118 self.steps.push(step);
119 }
120 pub fn current_step(&self) -> usize {
122 self.current_step
123 }
124 pub fn current_step_name(&self) -> Option<&str> {
126 self.steps.get(self.current_step).map(|s| s.name.as_str())
127 }
128 pub fn current_form(&self) -> Option<&Form> {
130 self.steps.get(self.current_step).map(|s| &s.form)
131 }
132 pub fn current_form_mut(&mut self) -> Option<&mut Form> {
134 self.steps.get_mut(self.current_step).map(|s| &mut s.form)
135 }
136 pub fn total_steps(&self) -> usize {
138 self.steps.len()
139 }
140 pub fn is_first_step(&self) -> bool {
142 self.current_step == 0
143 }
144 pub fn is_last_step(&self) -> bool {
146 self.current_step + 1 >= self.steps.len()
147 }
148 pub fn next_step(&mut self) -> Result<(), String> {
166 if self.is_last_step() {
167 return Err("Already at last step".to_string());
168 }
169
170 for i in (self.current_step + 1)..self.steps.len() {
172 if self.steps[i].is_available(&self.session_data) {
173 self.current_step = i;
174 return Ok(());
175 }
176 }
177
178 Err("No available next step".to_string())
179 }
180 pub fn previous_step(&mut self) -> Result<(), String> {
199 if self.is_first_step() {
200 return Err("Already at first step".to_string());
201 }
202
203 for i in (0..self.current_step).rev() {
205 if self.steps[i].is_available(&self.session_data) {
206 self.current_step = i;
207 return Ok(());
208 }
209 }
210
211 Err("No available previous step".to_string())
212 }
213 pub fn goto_step(&mut self, name: &str) -> Result<(), String> {
252 let target_index = self
254 .steps
255 .iter()
256 .position(|step| step.name == name && step.is_available(&self.session_data))
257 .ok_or_else(|| format!("Step '{}' not found or not available", name))?;
258
259 if target_index <= self.current_step {
261 self.current_step = target_index;
262 return Ok(());
263 }
264
265 for i in self.current_step..target_index {
268 let step_name = &self.steps[i].name;
269 if !self.session_data.contains_key(step_name) {
270 return Err(format!(
271 "Cannot skip to step '{}': step '{}' has not been completed",
272 name, step_name
273 ));
274 }
275 }
276
277 self.current_step = target_index;
278 Ok(())
279 }
280 pub fn save_step_data(
300 &mut self,
301 data: HashMap<String, serde_json::Value>,
302 ) -> Result<(), FormError> {
303 if let Some(step) = self.steps.get(self.current_step) {
304 self.session_data.insert(step.name.clone(), data);
305 Ok(())
306 } else {
307 Err(FormError::Validation("Invalid step".to_string()))
308 }
309 }
310 pub fn get_all_data(&self) -> &HashMap<String, HashMap<String, serde_json::Value>> {
312 &self.session_data
313 }
314 pub fn get_step_data(&self, step_name: &str) -> Option<&HashMap<String, serde_json::Value>> {
316 self.session_data.get(step_name)
317 }
318 pub fn clear_data(&mut self) {
320 self.session_data.clear();
321 self.current_step = 0;
322 }
323 pub fn process_step(
340 &mut self,
341 data: HashMap<String, serde_json::Value>,
342 ) -> Result<bool, FormError> {
343 if let Some(form) = self.current_form_mut() {
344 form.bind(data.clone());
345
346 if form.is_valid() {
347 self.save_step_data(data)?;
348
349 if !self.is_last_step() {
350 self.next_step().map_err(FormError::Validation)?;
351 Ok(false) } else {
353 Ok(true) }
355 } else {
356 Err(FormError::Validation("Form validation failed".to_string()))
357 }
358 } else {
359 Err(FormError::Validation("Invalid step".to_string()))
360 }
361 }
362 pub fn progress_percentage(&self) -> f32 {
364 if self.steps.is_empty() {
365 return 0.0;
366 }
367 ((self.current_step + 1) as f32 / self.steps.len() as f32) * 100.0
368 }
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374 use crate::fields::CharField;
375
376 #[test]
377 fn test_wizard_basic() {
378 let mut wizard = FormWizard::new("registration".to_string());
379
380 let mut form1 = Form::new();
381 form1.add_field(Box::new(CharField::new("username".to_string())));
382 wizard.add_step(WizardStep::new("account".to_string(), form1));
383
384 let mut form2 = Form::new();
385 form2.add_field(Box::new(CharField::new("email".to_string())));
386 wizard.add_step(WizardStep::new("contact".to_string(), form2));
387
388 assert_eq!(wizard.total_steps(), 2);
389 assert_eq!(wizard.current_step(), 0);
390 assert_eq!(wizard.current_step_name(), Some("account"));
391 assert!(wizard.is_first_step());
392 assert!(!wizard.is_last_step());
393 }
394
395 #[test]
396 fn test_wizard_navigation() {
397 let mut wizard = FormWizard::new("test".to_string());
398
399 for i in 1..=3 {
400 let mut form = Form::new();
401 form.add_field(Box::new(CharField::new(format!("field{}", i))));
402 wizard.add_step(WizardStep::new(format!("step{}", i), form));
403 }
404
405 assert_eq!(wizard.current_step(), 0);
406
407 wizard.next_step().unwrap();
408 assert_eq!(wizard.current_step(), 1);
409
410 wizard.next_step().unwrap();
411 assert_eq!(wizard.current_step(), 2);
412 assert!(wizard.is_last_step());
413
414 wizard.previous_step().unwrap();
415 assert_eq!(wizard.current_step(), 1);
416 }
417
418 #[test]
419 fn test_wizard_conditional_step() {
420 let mut wizard = FormWizard::new("test".to_string());
421
422 let mut form1 = Form::new();
423 form1.add_field(Box::new(CharField::new("type".to_string())));
424 wizard.add_step(WizardStep::new("type_selection".to_string(), form1));
425
426 let mut form2 = Form::new();
427 form2.add_field(Box::new(CharField::new("premium_field".to_string())));
428 let step2 = WizardStep::new("premium".to_string(), form2).with_condition(|data| {
429 data.get("type_selection")
430 .and_then(|d| d.get("type"))
431 .and_then(|v| v.as_str())
432 .map(|s| s == "premium")
433 .unwrap_or(false)
434 });
435 wizard.add_step(step2);
436
437 assert!(!wizard.steps[1].is_available(&wizard.session_data));
439
440 let mut data = HashMap::new();
442 data.insert("type".to_string(), serde_json::json!("premium"));
443 wizard.save_step_data(data).unwrap();
444
445 assert!(wizard.steps[1].is_available(&wizard.session_data));
446 }
447
448 #[test]
449 fn test_wizard_progress() {
450 let mut wizard = FormWizard::new("test".to_string());
451
452 for i in 1..=4 {
453 let mut form = Form::new();
454 form.add_field(Box::new(CharField::new(format!("field{}", i))));
455 wizard.add_step(WizardStep::new(format!("step{}", i), form));
456 }
457
458 assert_eq!(wizard.progress_percentage(), 25.0); wizard.next_step().unwrap();
461 assert_eq!(wizard.progress_percentage(), 50.0); wizard.next_step().unwrap();
464 assert_eq!(wizard.progress_percentage(), 75.0); wizard.next_step().unwrap();
467 assert_eq!(wizard.progress_percentage(), 100.0); }
469
470 #[test]
471 fn test_wizard_goto_step_backward_always_allowed() {
472 let mut wizard = FormWizard::new("test".to_string());
473
474 for i in 1..=3 {
475 let mut form = Form::new();
476 form.add_field(Box::new(CharField::new(format!("field{}", i))));
477 wizard.add_step(WizardStep::new(format!("step{}", i), form));
478 }
479
480 let mut data = HashMap::new();
482 data.insert("field1".to_string(), serde_json::json!("value"));
483 wizard.save_step_data(data.clone()).unwrap();
484 wizard.next_step().unwrap();
485 data.clear();
486 data.insert("field2".to_string(), serde_json::json!("value"));
487 wizard.save_step_data(data).unwrap();
488 wizard.next_step().unwrap();
489 assert_eq!(wizard.current_step(), 2);
490
491 wizard.goto_step("step1").unwrap();
493 assert_eq!(wizard.current_step(), 0);
494 assert_eq!(wizard.current_step_name(), Some("step1"));
495 }
496
497 #[test]
498 fn test_wizard_goto_step_forward_requires_completed_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 result = wizard.goto_step("step3");
509 assert!(result.is_err());
510 assert_eq!(wizard.current_step(), 0);
511 }
512
513 #[test]
514 fn test_wizard_goto_step_forward_after_completing_steps() {
515 let mut wizard = FormWizard::new("test".to_string());
516
517 for i in 1..=3 {
518 let mut form = Form::new();
519 form.add_field(Box::new(CharField::new(format!("field{}", i))));
520 wizard.add_step(WizardStep::new(format!("step{}", i), form));
521 }
522
523 let mut data = HashMap::new();
525 data.insert("field1".to_string(), serde_json::json!("value1"));
526 wizard.save_step_data(data).unwrap();
527
528 wizard.next_step().unwrap();
530 let mut data2 = HashMap::new();
531 data2.insert("field2".to_string(), serde_json::json!("value2"));
532 wizard.save_step_data(data2).unwrap();
533
534 wizard.goto_step("step3").unwrap();
536 assert_eq!(wizard.current_step(), 2);
537 assert_eq!(wizard.current_step_name(), Some("step3"));
538 }
539}