1use std::fmt;
2
3mod numeric_input;
4mod text_input;
5
6pub use numeric_input::*;
7pub use text_input::*;
8
9pub use structform_derive::*;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum ParseError {
14 Required,
15 InvalidFormat {
16 required_type: String,
17 },
18 FromStrError(String),
19 NumberOutOfRange {
20 required_type: String,
21 min: String,
22 max: String,
23 },
24}
25
26impl fmt::Display for ParseError {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 match self {
29 ParseError::Required => write!(f, "This field is required."),
30 ParseError::InvalidFormat { required_type } => write!(f, "Expected {}.", required_type),
31 ParseError::FromStrError(error) => write!(f, "{}.", error),
32 ParseError::NumberOutOfRange {
33 required_type,
34 min,
35 max,
36 } => write!(f, "Expected {} between {} and {}.", required_type, min, max),
37 }
38 }
39}
40
41pub trait StructForm<Model> {
42 type Field;
43
44 fn new(model: &Model) -> Self;
45 fn set_input(&mut self, field: Self::Field, value: String);
46
47 fn submit(&mut self) -> Result<Model, ParseError>;
48 fn submit_update(&mut self, model: Model) -> Result<Model, ParseError>;
49 fn submit_attempted(&self) -> bool;
50 fn is_empty(&self) -> bool;
51
52 fn has_unsaved_changes(&self, pristine: &Model) -> bool
53 where
54 Self: Clone,
55 Model: Clone + PartialEq,
56 {
57 let mut tmp_form = self.clone();
58 let updated_model = tmp_form.submit_update(pristine.clone());
59 match updated_model {
60 Ok(updated_model) => *pristine != updated_model,
61 Err(_) => true,
62 }
63 }
64
65 fn validation_error(&self) -> Option<ParseError>
66 where
67 Self: Clone,
68 {
69 if self.submit_attempted() {
76 self.clone().submit().err()
77 } else {
78 None
79 }
80 }
81}
82
83pub trait ParseAndFormat<T> {
89 fn parse(value: &str) -> Result<T, ParseError>;
90 fn format(value: &T) -> String;
91}
92
93#[macro_export]
95macro_rules! derive_form_input {
96 ($input:ident) => {
97 #[derive(Clone)]
98 pub struct $input<T> {
99 pub initial_input: String,
100 pub input: String,
101 pub value: Result<T, structform::ParseError>,
102 pub is_edited: bool,
103 }
104
105 impl<T> Default for $input<T>
106 where
107 $input<T>: structform::ParseAndFormat<T>,
108 {
109 fn default() -> $input<T> {
110 $input {
111 initial_input: String::new(),
112 input: String::new(),
113 value: $input::parse(""),
114 is_edited: false,
115 }
116 }
117 }
118
119 impl<T> $input<T> {
120 pub fn show_validation_msg(&self) -> bool {
121 self.is_edited && self.value.is_err()
122 }
123
124 pub fn validation_error(&self) -> Option<&structform::ParseError> {
125 self.value
126 .as_ref()
127 .err()
128 .filter(|_| self.show_validation_msg())
129 }
130
131 pub fn is_empty(&self) -> bool {
132 self.input.is_empty()
133 }
134 }
135
136 #[allow(dead_code)]
137 impl<T> $input<T>
138 where
139 $input<T>: structform::ParseAndFormat<T>,
140 T: Clone,
141 {
142 pub fn new(value: &T) -> $input<T> {
143 let initial_input = Self::format(value);
144 $input {
145 initial_input: initial_input.clone(),
146 input: initial_input,
147 value: Ok(value.clone()),
148 is_edited: false,
149 }
150 }
151
152 pub fn submit(&mut self) -> Result<T, structform::ParseError> {
153 self.is_edited = true;
154 self.value.clone()
155 }
156
157 pub fn set_input(&mut self, value: String) {
158 self.value = Self::parse(&value);
159 self.input = value;
160 self.is_edited = true;
161 }
162
163 pub fn clear(&mut self) {
164 self.initial_input = "".to_string();
165 self.set_input("".to_string());
166 self.is_edited = false;
167 }
168 }
169 };
170}