structform/
lib.rs

1use std::fmt;
2
3mod numeric_input;
4mod text_input;
5
6pub use numeric_input::*;
7pub use text_input::*;
8
9// Re-export this, so users don't need to explicitly depend on both crates.
10pub 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        // This is not an efficient implementation because it clones
70        // the whole form. It would be better if we had a separate
71        // immutable parse vs submit, or have some caching built into
72        // the form (model: Option<Result<Model>> updated on each
73        // input event?). It could be better to move this over to
74        // structform_derive.
75        if self.submit_attempted() {
76            self.clone().submit().err()
77        } else {
78            None
79        }
80    }
81}
82
83/// Trait used to tie strongly typed models into form
84/// inputs. Libraries must define their own form inputs (although
85/// macros are provided to make this easy), and then implement
86/// `ParseAndFormat` for their input for all supported types. In other
87/// words, `impl ParseAndFormat<MyType> for MyTextInput<MyType>`.
88pub trait ParseAndFormat<T> {
89    fn parse(value: &str) -> Result<T, ParseError>;
90    fn format(value: &T) -> String;
91}
92
93/// Creates a new form input to be used in a StructForm.
94#[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}